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;

处理完的情况如图:

在这里插入图片描述

关于压缩内存后跟新对象的引用,下文介绍.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值