kvm垃圾收集-壓縮算法實現,獨家

本文介紹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的取值有兩種情況:

  1. 零值: 此時的情況爲liveEnd = scanner = currentHeapEnd,也就是當前free chunk 爲null的情況

  2. 非零值: 此時的情況如圖所示:

    在這裏插入圖片描述

然後調用slideObject方法,這是核心方法,也很複雜.此處分開進行講解.

在該方法中首先計算BreakTable的大小.代碼如下:

int tableSize = tableLength * sizeof(table[0]);
int fullTableSize = tableSize + sizeof(table[0]);
int freeSize;

freeSize = PTR_DELTA(table, target);

注意,此時的內存佈局如圖:

在這裏插入圖片描述

那麼此處處理方式有5種:

  1. objectSize <= freeSize.(注意,objectSize也就是存活對象的大小).此時只需移動即可,因爲,table還是處於對象存活一端的尾部.代碼如下:

    if (objectSize <= freeSize) {// 1.
         
         memmove(target, object, objectSize);
         return table;
     }
    
  2. 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種了…):

  1. 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;
 }

此時處理完的情況如圖:

在這裏插入圖片描述

  1. 此時的情況爲 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;

處理完的情況如圖:

在這裏插入圖片描述

關於壓縮內存後跟新對象的引用,下文介紹.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章