半区复制算法

作者: 可文分身 | 来源:发表于2014-02-20 15:40 被阅读676次

前言

半区复制算法的目的也是为了更好的缓解内存碎片问题。对比于标记-压缩算法, 它不需要遍历堆内存那么多次,节约了时间,但是它也带来了一个主要的缺点,那就是相比于标记-清除和标记-压缩垃圾回收器,它的可用堆内存减少了一半。同时对于大对象,复制比标记的代价更大。所以半区复制算法更一般适合回收小的,存活期短的对象。

三色抽象法

在我们深入半区复制算法原理前,我们需要了解下什么是三色抽象法。对于一个对象,垃圾收集器可以将其标记为灰色,黑色和白色中的一种,每种颜色代表不同的含义,

  1. 灰色 - 表示垃圾收集器已经访问过该对象,但是还没有访问过它的所有孩子节点。
  2. 黑色 - 表示该对象以及它的所有孩子节点都已经被垃圾收集器访问过了。
  3. 白色 - 表示该对象从来没有被垃圾收集器访问过,这就是非可达对象。

三色抽象法也可以用在标记-清除算法和标记-压缩算法。当垃圾收集结束后,可达对象都被标记为黑色,非可达对象都被标记为白色,不会有灰色对象存在。在半区复制算法里,我们也采用了三色抽象法来标记对象。

算法原理

之所以叫半区复制,是因为它将堆内存对半分为两个半区,只用其中一个半区来进行对象内存的分配,如果在这个半区内存不够给新的对象分配了,那么就开始进行垃圾收集,将这个半区中的所有可达对象都拷贝到另外一个半区中去,然后继续在另外那个半区进行新对象的内存分配。半区复制算法中比较常见是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对象是根对象。

  1. 首先先交换左右半区(ToSpace, FromSpace), 同时设置free指针和top指针。
  2. 遍历处理根对象A,B。先将A对象复制到free指针指向的位置,同时将A对象复制后的地址(迁移地址)写到原先A对象所在的位置,图中虚线的箭头表示。可以看到A对象已经被collector访问过了,但是还没有访问其孩子节点,所以将其标为了灰色。紧接着scan,free指针继续向前移动。
  3. 由于是深度遍历算法,紧接collector会先遍历处理A对象所引用的对象C,当发现对象C没有迁移地址时,说明它还没有被复制,由于它又是可达对象,所以接着collector会将它复制到当前free指针指向的位置,即对象A后面。对象C复制完后,会用其复制后的地址来更新A原先对C的引用,同时也写到原先C对象所在的地址上。
  4. 接着collector会处理对象C的孩子节点(深度遍历算法),由于对象C没有引用任何对象,于是对象C的处理结束,将其标记为黑色。然后collector接着处理A对象的另外一个孩子节点E对象,处理方式跟处理对象C一致。
  5. 对象E也没有孩子节点,collector也将其标识为黑色。
  6. 到目前为此,A对象也全部处理结束了,于是collector将其标识为黑色,然后接着去处理对象B。当复制B对象结束后,发现B对象所引用的对象C有迁移地址,于是就更新其对对象C的引用,使其指向FromSpace半区中对象C的迁移地址 - 即C对象复制后所在ToSpace的地址。这个情况下就不需要再次复制对象C了。
  7. 当所有的可达对象都从FromSpace半区复制到ToSpace半区后,垃圾收集结束。新对象的内存分配从free指针指向的位置开始进行分配。

其实,从垃圾收集过程中对象的移动顺序来看,collector将相邻的对象都复制在相近的位置上,它属于前面我们在标记-压缩算法里所说的“线性顺序”。

相关文章

  • 半区复制算法

    前言 半区复制算法的目的也是为了更好的缓解内存碎片问题。对比于标记-压缩算法, 它不需要遍历堆内存那么多次,节约了...

  • java常用GC算法

    (1).复制收集算法 针对Young区,依次扫描这个区的所有可达对象(如何确定可达对象,请参考前一节),扫描...

  • JVM

    eden区申请空间失败,会发生GC 老年代不能用复制算法,因为他只有一个区域,所以不能复制。所以老年代一般用标记整...

  • Java GC与四种引用

    常见的垃圾收集算法 复制(Copying)算法,我前面讲到的新生代GC,基本都是基于复制算法,将活着的对象复制到t...

  • 内存分配与回收策略

    相关概念: Eden区和survivor区:现在的商业虚拟机大多采用复制算法来回收新生代,但大多数新生代中的对象存...

  • jvm 基础篇-(5)- 垃圾回收算法--->复制算法(-XX:

    1、复制算法 复制(Copying)算法说到底也是为了解决 标记-清除算法 产生的那些碎片问题。 首先将内存分为大...

  • 每日一题(垃圾回收)

    赞同最多的解析 两个最基本的java回收算法:复制算法和标记清理算法复制算法:两个区域A和B,初始对象在A,继续存...

  • 复制算法

    为了解决标记清除算法内存碎片化严重的缺陷,提出了复制算法。复制算法主要思想是,按内存容量将内存划分为大小相等的两块...

  • 复制算法

    定义 把活动对象复制到其他空间,回收原空间所有对象。原空间称为From区,新空间称为To区。 过程概要 copyi...

  • 复制算法

网友评论

  • ybin:深度优先遍历,理解了,感谢!

本文标题:半区复制算法

本文链接:https://www.haomeiwen.com/subject/hbuhtttx.html