Java-GC-标记压缩算法

标记压缩算法

其分为两个阶段标记阶段,和压缩阶段.其中标记阶段和标记清除算法的标记阶段是一样的.

  • 对压缩算法来说,他的工作就是移动所有的可达对象到堆内存的同一区域中,使它们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存集中在一起,以防出现标记清除算法的弊端.

在压缩阶段,由于要移动可达对象,那么就要考虑移动对象时候的顺序问题,一般分为一下三种:

  1. 任意顺序,不考虑原先对象的排列顺序,也不考虑对象之间的引用关系,随意移动可达对象,这样可能会有内存访问的局部性问题.
  2. 线性顺序,在重新排列对象时,会考虑到对象之间的引用关系,例如对象A引用了对象V,那么就会尽量将对象A,B排列在一起.
  3. 滑动排序,按照原先的排列顺序滑动到堆的另一端.
    现在大部分垃圾回收算法都是按照任意顺序或者是滑动顺序去实现的.

Two-Finger算法

其实质是任意顺序移动,其最适合处理包含固定大小对象的内存区域.

该算法需要遍历堆内存两次,第一次是将堆尾的可达对象移动到堆开始的空闲内存单元(所以在前面说,最适合固定对象长度).第二次遍历则需要修改可达对象的引用,因为一些可达对象已经被移动到别的地址,而原先引用它们的对象还指向之前的地址.在这两次的遍历过程中,首位两个指针分别从对的头尾两个位置向中间移动,直至两个指针相遇.

LISP2算法

lisp2算法是一种应用更加广泛的压缩算法,它属于滑动顺序算法的一种.他和二指算法的不同之处在于它还可以处理大小不同的对象,而不是固定大小的对象(因此适用范围更加广泛).同时,计算出来的可达对象的迁移地址需要额外的空间进行存储而不是复写原先对象所在的位置.最后Lisps2需要三次堆内存的遍历.

第一次遍历

collector仅仅计算和记录可达对象应该迁移过去的地址.

compact():
    computeLocations(HeapStart,HeapEnd,HeapStart)
    updateReferences(HeapStart,HeapEnd)
    relocate(HeapStart,HeapEnd)
    
computeLocations(start,end,toRegion):
    scan <- start
    free <- toRegion
    while scan < end
        if isMarked(scan)
            forwardingAddress(scan) <- free
            free <- free + size(scan)
        scan <- scan + size(scan)

  1. 指针free和scan同时指向起始堆位置,同时scan开始向堆尾移动,目的是要找到被标记的可达对象.

  2. 发现可达对象之后,scan指针对应的位置分配一个额外的空间来存储该可达对象应该迁移到的地址,就是free指针指向的位置0,同时free指针向堆尾移动B对象大小的距离,free指针指向的位置.最后指针继续向前走,直到寻找到下一个可达对象D scan指针指向的位置.

  3. 同理,在可达对象D处分配一块空间来保存对象D应该迁移到的位置,由于B对象已经占用了2个内存单元,所以对象E的迁移地址是从位置2开始,也就是当前free指针指向的位置。

  4. 指针free,scan继续向前移动。

  5. 第一次遍历完后,所有的可达对象都有了对应的迁移地址,free指针指向位置9,因为所有的可达对象总共占了9个单元大小的空间。

第二次遍历

第二次遍历主要是修改对象之间的引用关系,基本和二指算法的第二次遍历一样.

updateReferences(start,end):
    for each fld in Roots
        ref <- *fld
        if ref != null
            *fld <- forwardingAddress(ref)
    
    scan <- start
    while scan < end
        if isMarked(scan)
            for each fld in Pointers(scan)
                if *fld != null
                    *fld <- forwardingAddress(*fld)
        scan <- scan + size(scan)

  1. 修改根对象的引用关系,根对象1引用对象B,对象B的迁移地址为0,于是collector将根对象对B对象的引用指向它的迁移地址 - 位置0, 现在A对象所处的位置。

  2. 同理,对于根对象2,3都执行同样的操作,将它们对其所引用的对象的引用修改为对应的它们所引用的对象的迁移地址。

  3. 通过scan指针遍历堆内存,更新所有的可达对象对其引用对象的引用为其引用对象的迁移地址。比如说,对于可达对象B,它引用了对象D,D的迁移地址是2,那么B直接将其对D对象的引用重新指向2这个位置。

  4. 第二次遍历结束后的对象之间的引用关系。

第三次遍历

第三次遍历则是根据可达对象的迁移地址去移动可达对象,比如说可达对象B,它的迁移地址是0,那么就将其移动到位置0,同时去除可达对象的标记,以便下次垃圾收集。

relocate(start,end):
    scan <- start
    while scan < end
        if isMarked(scan)
            dest <- forwardingAddress(scan)
            move(scan,dest) //将可达对象从scan位置移动到dest位置
            unsetMarked(dest)
        scan <- scan + size(scan)

posted @ 2019-05-22 20:19  X-POWER  阅读(1485)  评论(0编辑  收藏  举报