本文介紹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;
處理完的情況如圖:
關於壓縮內存後跟新對象的引用,下文介紹.