半区复制算法
2014-02-20 本文已影响676人
可文分身
前言
半区复制算法的目的也是为了更好的缓解内存碎片问题。对比于标记-压缩算法, 它不需要遍历堆内存那么多次,节约了时间,但是它也带来了一个主要的缺点,那就是相比于标记-清除和标记-压缩垃圾回收器,它的可用堆内存减少了一半。同时对于大对象,复制比标记的代价更大。所以半区复制算法更一般适合回收小的,存活期短的对象。
三色抽象法
在我们深入半区复制算法原理前,我们需要了解下什么是三色抽象法。对于一个对象,垃圾收集器可以将其标记为灰色,黑色和白色中的一种,每种颜色代表不同的含义,
- 灰色 - 表示垃圾收集器已经访问过该对象,但是还没有访问过它的所有孩子节点。
- 黑色 - 表示该对象以及它的所有孩子节点都已经被垃圾收集器访问过了。
- 白色 - 表示该对象从来没有被垃圾收集器访问过,这就是非可达对象。
三色抽象法也可以用在标记-清除算法和标记-压缩算法。当垃圾收集结束后,可达对象都被标记为黑色,非可达对象都被标记为白色,不会有灰色对象存在。在半区复制算法里,我们也采用了三色抽象法来标记对象。
算法原理
之所以叫半区复制,是因为它将堆内存对半分为两个半区,只用其中一个半区来进行对象内存的分配,如果在这个半区内存不够给新的对象分配了,那么就开始进行垃圾收集,将这个半区中的所有可达对象都拷贝到另外一个半区中去,然后继续在另外那个半区进行新对象的内存分配。半区复制算法中比较常见是Cheney算法。
下面是复制算法的伪代码:
atomic collect():
flip()
scan <- free
for each fld in Roots
process(fld)
while not isEmpty(worklist)
ref <- remove(worklist)
scan(ref)
flip():
fromspace, tospace <- tospace, fromspace
top <- tospace+extent //界定堆内存的边界
free <- tospace
scan(ref):
for each fld in Pointers(ref)
process(fld)
process(fld):
fromRef <- *fld
if fromRef != null
*fld <- forward(fromRef) //将可达对象复制后的地址写到原对象的位置上,当作迁移地址
forward(fromRef):
toRef <- forwardingAddress(fromRef) //读取该位置上对象的迁移地址
if toRef == null //如果该位置上的对象没有迁移地址,那就说明它还没有被复制,需要复制到tospace中去
toRef <- copy(fromRef)
return toRef
copy(fromRef):
toRef <- free
free <- free + size(fromRef)
move(fromRef, toRef) //从fromRef位置复制对象到toRef位置上
forwardingAddress(fromRef) <- toRef //将地址toRef写到fromRef位置上的对象中去
add(worklist, toRef)
return toRef
remove(worklist):
ref <- scan
scan <- scan + size(scan)
return ref
实际上半区复制算法的实现跟标记-压缩算法的实现差不多, 都是采用的深度遍历算法,理解该算法的关键点是,怎么计算可达对象的迁移地址(forwardingAddress) - 看copy(fromRef)方法的实现, 以及怎么更新对象间的引用关系。
半区复制算法的过程如下:
复制算法
我们假设A,B对象是根对象。
- 首先先交换左右半区(ToSpace, FromSpace), 同时设置free指针和top指针。
- 遍历处理根对象A,B。先将A对象复制到free指针指向的位置,同时将A对象复制后的地址(迁移地址)写到原先A对象所在的位置,图中虚线的箭头表示。可以看到A对象已经被collector访问过了,但是还没有访问其孩子节点,所以将其标为了灰色。紧接着scan,free指针继续向前移动。
- 由于是深度遍历算法,紧接collector会先遍历处理A对象所引用的对象C,当发现对象C没有迁移地址时,说明它还没有被复制,由于它又是可达对象,所以接着collector会将它复制到当前free指针指向的位置,即对象A后面。对象C复制完后,会用其复制后的地址来更新A原先对C的引用,同时也写到原先C对象所在的地址上。
- 接着collector会处理对象C的孩子节点(深度遍历算法),由于对象C没有引用任何对象,于是对象C的处理结束,将其标记为黑色。然后collector接着处理A对象的另外一个孩子节点E对象,处理方式跟处理对象C一致。
- 对象E也没有孩子节点,collector也将其标识为黑色。
- 到目前为此,A对象也全部处理结束了,于是collector将其标识为黑色,然后接着去处理对象B。当复制B对象结束后,发现B对象所引用的对象C有迁移地址,于是就更新其对对象C的引用,使其指向FromSpace半区中对象C的迁移地址 - 即C对象复制后所在ToSpace的地址。这个情况下就不需要再次复制对象C了。
- 当所有的可达对象都从FromSpace半区复制到ToSpace半区后,垃圾收集结束。新对象的内存分配从free指针指向的位置开始进行分配。
其实,从垃圾收集过程中对象的移动顺序来看,collector将相邻的对象都复制在相近的位置上,它属于前面我们在标记-压缩算法里所说的“线性顺序”。