GC算法-标记压缩算法

 

概述

还记得标记清除复制算法的问题么? 堆使用效率低和碎片化问题. 那么有没有能够利用整个堆, 有没有内存碎片化问题的算法呢? 这就是标记压缩算法了.

简单来说, 标记压缩算法就是将堆中的所有活动对象整体向左移, 将对象间的空隙消除.

在GC执行前的内存:

GC执行后的内存:

恩, 就是这么个意思.

实现

如何实现上面的操作呢? 首先, 要将所有活动对象标记出来. 这是标记阶段, 跳过了, 跟标记清除一样操作就行. (这里每个对象都有一个mark属性, true为活动对象)

标记完了, 那就剩下压缩操作了. 如何进行呢?

  1. 遍历堆, 将所有活动对象挪到左边. 但是, 后面有对象引用了前边的对象, 你就找不到新的指针了, 因为那块地址很可能已经被覆盖了.

  2. ....

最后想了想, 还是得老老实实地三步走:

  1. 遍历堆, 将所有对象通过计算得到新的地址并保存

  2. 遍历堆, 将所有子对象的地址更新为新的地址, 同时更新根集合中的指针.

  3. 遍历堆, 将对象集体迁移. 指针的问题都解决了, 可以将对象搬到新家了.

步骤一: 计算所有对象的新地址

// HEAP_START 是堆的开始位置, HEAP_END 是堆得结束位置
obj = HEAP_START
newAddr = HEAP_START
// 遍历所有活动对象
while(obj < HEAP_END){
    // 非活动对象, 跳过
    if(obj.mark != true){
        obj += obj.size;
        continue;
    }
    // 记录新的地址
    obj.newAddr = newAddr
    newAddr += obj.size
    // 继续遍历
    obj += obj.size
}

 

这遍完后, 所有活动对象都保存了自己的新地址, 然后就可以将所有指针的地址进行更新了.

步骤二: 更新所有指针

// 更新根集合中的指针
for(obj in roots){
    obj = obj.newAddr
}
/*
更新所有活动对象的指针
当然, 这里也可以修改为遍历所有活动对象, 并将指针进行更新. 但是会出现各种重复处理、指针覆盖等问题, 就直接遍历堆了. 
*/
obj = HEAP_START
while(obj < HEAP_END){
    if(obj.mark != true){
        obj += obj.size;
        continue;
    }
    // 更新子对象
    for(child in children){
        child = child.newAddr
    }
    obj += obj.size
}

 


至此, 所有指针都已经更新完毕, 但是, 对象还没有移动. 只剩下最后一步了, 将对象按照步骤一的规律, 向左排排坐就好啦.

步骤三: 迁移对象

obj = newAddr = HEAP_START
while(obj < HEAP_END){
    if(obj.mark != true) {
        obj += obj.size;
        continue;
     }
    // 将obj的数据复制到newAddr处
    copyData(newAddr, obj, obj.size);
    // 清空数据, 为下一次GC做准本
    newAddr.mark = false;
    newAddr.newAddr = null;
    // 遍历下一个对象
    obj += obj.size
    newAddr += obj.size
}

 

至此, 实现基本完成. 创建对象分配内存的操作与复制算法一样. 这个算法简直是融合了标记清除复制算法的优点, 解决了他们的问题, 不光堆的使用效率变高了, 而且也没有内存碎片的问题了. 但是, 就是, 只不过要对堆进行三次遍历而已. 不过没关系啦, 毕竟有失才有得嘛. 不过是时间换空间了.

而这, 也是标记压缩算法最大的问题了, 执行时间太久了, 标记清除对堆进行一次遍历, 而标记压缩要进行三次. 三倍的时间. 可想而知.

不过也有伟人说了, 算法没有好不好, 只有是否适合. 这几种可达性的算法各有优劣吧.

标记压缩的衍生

Two-Finger算法

将堆的遍历次数减少到两次.

如上图所示, 在第一次遍历的时候, 指针1从前向后寻找空闲地址, 指针2从后向前寻找活动对象, 找到后在原地址中记录新地址, 并将对象进行复制.

第二次遍历就可以将所有对象中的指针进行更新了.

你也发现了, 这个算法如果不想发生内存碎片化, 那就只能令每个对象的空间都是相同的. 而事实上也确实是这样. 强行规定每个对象都占用相同大小的空间, 我不知道这算法有什么应用场景. (原谅我的无知)

其他

还有一些其他的表格算法lmmixGC算法等, 因为这两个我看的似懂非懂, 就不细说了.


标记压缩算法差不多就这么些. 告辞~~~

你可能感兴趣的:(GC算法-标记压缩算法)