本文介绍kvm中垃圾收集算法中的压缩部分.
这里涉及到BreakTable 这么一个数据结构,BreakTable的每个表项对应于在garbage collector进行compating的过程中每个被移动了的object,每个表项含有两个字段,其中address表示该项对应的object被移动前在内存中的起始地址,offset表示该object在compacting过程中相对于原地址移动的偏移量。这样,gc 在compacting的同时填写该表,compacting完成后再遍历这个BreakTable.就达到了移动指针的目的. 其定义如下:
typedef struct breakTableStruct {
int length; /* in entries */
struct breakTableEntryStruct *table;
} breakTableStruct;
typedef struct breakTableEntryStruct {
cell *address;
int offset;
} breakTableEntryStruct;
压缩内存的代码如下:
static cell*
compactTheHeap(breakTableStruct *currentTable, CHUNK firstFreeChunk)
{
cell* copyTarget = CurrentHeap;
cell* scanner; /* current object 当前对象 */
int count; /* break table的数量*/
cell* currentHeapEnd = CurrentHeapEnd; /* cache for speed */
int lastRoll = 0; /* break table 的数量 */
CHUNK freeChunk = firstFreeChunk;
breakTableEntryStruct *table = NULL;
for (scanner = CurrentHeap, count = -1; ; count++) {
/* 跳过存活对象 */
cell *live, *liveEnd;
live = scanner;
if (freeChunk != NULL) {
liveEnd = (cell *)freeChunk;
// ((cell)(*liveEnd)) >> 8
scanner = liveEnd + SIZE(*liveEnd) + HEADERSIZE; // 指向当前free chunk的终点
freeChunk = freeChunk->next; // 更新free 指针
} else { // 如果free chunk为null,则说明此时已经没有空闲内存了
liveEnd = scanner = currentHeapEnd;
}
if (count < 0) {
/*
* 这是内存开头的一块活动对象。不需要复制。
*/
copyTarget = liveEnd;
} else {
/* The size of the chunk of live objects */
int liveSize = PTR_DELTA(liveEnd, live); // 计算存活对象的个数
if (count == 0) {
int i;
/*
* 将break table移动到末尾
*/
// 进行移动
memmove(copyTarget, live, liveSize);
table = (breakTableEntryStruct*)scanner - 1;
} else {
int extraSize = PTR_DELTA(scanner, liveEnd); // 计算此时可用内存的大小
table = slideObject(copyTarget, live, liveSize, extraSize,
table, count, &lastRoll);
}
/* Add a new entry to the break table */
table[count].address = live;
table[count].offset = PTR_DELTA(live, copyTarget);
/* And update copy target. 跟新copyTarget */
copyTarget = PTR_OFFSET(copyTarget, liveSize);
}
if (scanner >= currentHeapEnd) {
if (INCLUDEDEBUGCODE && scanner > currentHeapEnd) {
fatalVMError(KVM_MSG_SWEEPING_SCAN_WENT_PAST_HEAP_TOP);
}
break;
}
}
if (lastRoll > 0) {
// 将BreakTable 中进行排序
sortBreakTable(table, lastRoll);
}
currentTable->table = table;
currentTable->length = count + 1;
/* 返回内存中第一个空闲内存 */
return copyTarget;
}
此时内存中是分有很多区域的,由存活对象,free区域组成.如图所示:
因此,此处就遍历整个堆,进行移动.
cell *live, *liveEnd;
live = scanner;
if (freeChunk != NULL) {
liveEnd = (cell *)freeChunk;
// ((cell)(*liveEnd)) >> 8
scanner = liveEnd + SIZE(*liveEnd) + HEADERSIZE; // 指向当前free chunk的终点
freeChunk = freeChunk->next; // 更新free 指针
} else { // 如果free chunk为null,则说明此时已经没有空闲内存了
liveEnd = scanner = currentHeapEnd;
}
此处是为了跳过存活的对象,找到free chunk的地址. 注意,free chunk 是链表结构,如果此处free chunk 为null,则说明,此时堆中已没有空闲内存了.
if (count < 0) {
/*
* 这是内存开头的一块活动对象。不需要复制。
*/
copyTarget = liveEnd;
}
copyTarget 指的是移动对象最终地址的起点.如果count < 0 ,则说明是该循环的第一次执行. 那么此时就只需将copyTarget 设置为liveEnd 即可,这样,移动对象后,就达到了压缩的目的.此时的情况如图所示:
接下来执行如下代码(此时符合的情况为: 当前的循环次数为2):
int liveSize = PTR_DELTA(liveEnd, live); // 计算存活对象的个数
if (count == 0) {
int i;
/*
* 将break table移动到末尾
*/
// 进行移动
memmove(copyTarget, live, liveSize);
table = (breakTableEntryStruct*)scanner - 1;
}
/* Add a new entry to the break table */
table[count].address = live;
table[count].offset = PTR_DELTA(live, copyTarget);
copyTarget = PTR_OFFSET(copyTarget, liveSize);
此处移动后的情况如图所示:
以上是一种情况,还有另一种情况: 那就是当前的循环次数>=3. 那么此时的任务除了移动对象还有移动BreakTable(因为BreakTable分配在存活对象一端的尾部).
此处首先计算的是可用内存的大小.如下:
int extraSize = PTR_DELTA(scanner, liveEnd); // 计算此时可用内存的大小
extraSize的取值有两种情况:
-
零值: 此时的情况为liveEnd = scanner = currentHeapEnd,也就是当前free chunk 为null的情况
-
非零值: 此时的情况如图所示:
然后调用slideObject方法,这是核心方法,也很复杂.此处分开进行讲解.
在该方法中首先计算BreakTable的大小.代码如下:
int tableSize = tableLength * sizeof(table[0]);
int fullTableSize = tableSize + sizeof(table[0]);
int freeSize;
freeSize = PTR_DELTA(table, target);
注意,此时的内存布局如图:
那么此处处理方式有5种:
-
objectSize <= freeSize.(注意,objectSize也就是存活对象的大小).此时只需移动即可,因为,table还是处于对象存活一端的尾部.代码如下:
if (objectSize <= freeSize) {// 1. memmove(target, object, objectSize); return table; }
-
extraSize >= fullTableSize.此时处理的代码如下:
if (extraSize >= fullTableSize) { // newTable = ((void *)((char *)object + objectSize + extraSize - fullTableSize)) // 计算newTable的位置 cell *newTable = PTR_OFFSET(object, objectSize + extraSize - fullTableSize); /* for (i = 0; i < tableSize; i += CELL) { * CELL_AT_OFFSET(newTable, i) = CELL_AT_OFFSET(table, i); * } */ // 复制breaktable memmove(newTable, table, tableSize); /* for (i = 0; i < objectSize; i += CELL) { * CELL_AT_OFFSET(target, i) = CELL_AT_OFFSET(object, i); * } */ // 复制对象 memmove(target, object, objectSize); return (breakTableEntryStruct *)newTable; }
如果不是以上两种,则需要先将对象移动到target处.此时的代码如下:
memmove(target, object, freeSize);
object = PTR_OFFSET(object, freeSize);
objectSize -= freeSize;
/* Set freeSize to be the space between the table and the object 设置freeSize */
freeSize = PTR_DELTA(object, table) - tableSize; // ((char *)(object) - (char *)table) -tableSize
此时的情况如图:
那么此时的处理有3种(加上之前的2种就是5种了…):
-
fullTableSize <= objectSize.此时的处理代码如下:
if (fullTableSize <= objectSize) { breakTableEntryStruct *oldTable = table; breakTableEntryStruct *newTable = (breakTableEntryStruct*)object; // 进行交换 for (i = 0; i < tableSize; i += CELL) { cell temp = CELL_AT_OFFSET(table, i);// (*(cell *)((void *)((char *)table + i))) CELL_AT_OFFSET(table, i) = CELL_AT_OFFSET(object, i); CELL_AT_OFFSET(object, i) = temp; } object = PTR_OFFSET(object, tableSize); objectSize -= tableSize; target = PTR_OFFSET(oldTable, i); table = newTable; goto moreFreeSpaceBeforeTable; }
移动后,其内存布局和该方法一开始的布局一样,因此又会从头开始处理.移动后的内存布局如图:
2. fullTableSize > objectSize && fullTableSize <= objectSize + freeSize.此时的情况是:
此时处理的代码为:
if (fullTableSize > objectSize && fullTableSize <= objectSize + freeSize) {
cell *oldTable = (cell *)table;
cell *newTable = PTR_OFFSET(object, objectSize - fullTableSize); // ((void *)((char *)object + objectSize - fullTableSize))
for (i = 0; i < objectSize; i += CELL) {
CELL_AT_OFFSET(newTable, i) = CELL_AT_OFFSET(oldTable, i); // (*(cell *)((void *)((char *)newTable + i))) =(*(cell *)((void *)((char *)oldTable + i)))
CELL_AT_OFFSET(oldTable, i) = CELL_AT_OFFSET(object, i); // (*(cell *)((void *)((char *)oldTable + i))) = (*(cell *)((void *)((char *)object + i)))
}
/* Do not use memmove here! Index 'i' is initialized above */
for ( ; i < tableSize; i += CELL) {
CELL_AT_OFFSET(newTable, i) = CELL_AT_OFFSET(oldTable, i); // 复制剩下的table
}
return (breakTableEntryStruct*)newTable;
}
此时处理完的情况如图:
- 此时的情况为 fullTableSize > objectSize && fullTableSize > objectSize + freeSize.此时的情况如图:
此时处理的代码为:
{
cell* endTable = PTR_OFFSET(table, tableSize); // 获得table 的结束指针
// 进行交换
for (i = 0; i < objectSize; i += CELL) {
cell temp = CELL_AT_OFFSET(table, i);
CELL_AT_OFFSET(table, i) = CELL_AT_OFFSET(object, i);
CELL_AT_OFFSET(endTable, i) = temp;
}
table = PTR_OFFSET(table, objectSize);
if (objectSize & CELL) { // 如果objectSize 不是4的倍数,则需要进行填充
/* We did an add number of rolls. We need to roll one more. */
if (freeSize + extraSize > 2 * CELL) {
/* We can just roll one more time. Remember that we are
* required that there be two free words at the end of
* the break table when we return */
CELL_AT_OFFSET(table, tableSize) = CELL_AT_OFFSET(table, 0); //(*(cell *)((void *)((char *)table + tableSize))) =(*(cell *)((void *)((char *)table + 0))) 复制一遍table
table = PTR_OFFSET(table, CELL); // 修改table 指针 ((void *)((char *)table + 4))
} else {
cell temp = CELL_AT_OFFSET(table, 0); // (*(cell *)((void *)((char *)table + 0)))
memmove(table, PTR_OFFSET(table, CELL), tableSize - 4);
CELL_AT_OFFSET(table, tableSize - 4) = temp;
}
}
*lastRoll = tableLength;
处理完的情况如图:
关于压缩内存后跟新对象的引用,下文介绍.