垃圾回收算法

409 阅读17分钟

[top]

1,标记清除算法
  1. 简述

    从根对象进行依次查找引用的对象,如果对象被其他对象所引用进行标记,我们在搜索对象并进行标记时使用的是深度优先搜索(depth-first search)。在清除阶段时遍历整个堆,将没有标记的对象进行清除,然后将清除的块链接到空闲链表上。如下几张图表现了标记清除算法的过程

  2. 基础算法

                          图1 执行GC前堆的状态,箭头指向为引用关系
    

                图2 执行标记后堆的状态,箭头指向为引用关系,对号表示标记了的对象
    

               图3 执行清除后堆的状态,箭头指向为引用关系,空白块表示清除了的对象
    

    算法源码

    New():
       ref←allocate()
       //堆中无可用空间
       if ref = null
          //回收
          collect()
          //再次分配空间
          ref←allocate()
          //还是分配不到
          if ref = null
             error "Out of memory"
       return ref
    //atomic 标记线程安全
    atomic collect():
       //从root指针域开始标记
       markFromRoots()
       //清扫
       sweep(HeapStart, HeapEnd)
       
    markFromRoots():
        //初始化工作列表
       initialise(worklist)
       //遍历根指针域
       for each fld in Roots
            ref←*fld
            //没有标记的放到工作空间
            if ref != null && not isMarked(ref)
                setMarked(ref)
                add(worklist, ref)
                mark()
     mark():
        while not isEmpty(worklist)
           //获取工作空间的ref
           ref←remove(worklist)
           //遍历ref的指针域,如果指针域中引用的对象没有标记添加到工作列表
           for each fld in Pointers(ref)
               child←*fld
               if child != null & not isMarked(child)
                  setMarked(child)
                  add(worklist, child) 
  1. 位图标记法

    GC标记-清除基础算法是在对象头中设置标记位,对象头中还存在其他信息如锁信息,哈希值等信息会增加写冲突,在linux系统下会造成不必要的写时复制技术的开销。为此可以将原本在对象头中存在的标记位与对象头分开,使用一个单独的表格进行存储,这个表格就是位图表格,这种技术可以称之为位图标记。标记时将对象所在位图表中标记为1,清除时遍历整个堆只需要把位图表中为0的链接到空闲链表就行。

    下图标记阶段之后的内存分布:

    image.png

    下图清除阶段之后,将位图为0的块连到空闲链表上:

    image.png

    算法源码

    /**
     *位图的标记算法
     *从位图中获取对象,放到工作列表中
     */
    mark():
      cur←nextInBitmap()
      while cur < HeapEnd 
        add(worklist, cur)
        markStep(cur)
        cur←nextInBitmap()
    //处理对象的指针域    
    markStep(start):
      while not isEmpty(worklist)
        ref←remove(worklist)
        for each fld in Pointers(ref)
          child<-*fld
          if child != nu11 && not isMarked(child)
            setMarked(child)
            if child < start
              add(worklist, child)
  1. 惰性清扫

    GC标记-清除基础算法中清除操作是与堆的大小成正比,会影响用户线程的停顿时间。惰性清除法在将内存划分为块的基础上,依然需要对堆进行标记但是不清理只有在分配时进行清除,这里的清扫不会对整个堆进行清除,只会找到连续的空间满足待分配空间的大小进行清除,简单的说就是比如需要10k的空间,惰性清扫就只会清扫10k或大于10k的空间,而不会对堆过多或全部清扫。如果在清除过程能获取分块就直接返回,如果不能则执行标记后再次执行清除。

    image.png

    算法源码

   //回收
   atomic collect():
      //从根上进行标记
      markFromRoots()
      //遍历块
      for each block in Blocks
        /**
         *如果内存块中不存在标记的对象将内存块还给分配器(这个内存块可以用),
         *否则将内存块添加到惰性清扫队列
         */
        if not isMarked(block)
          add(blockAllocator, block)
        else
          add(reclaimList, block)
          
    //分配内存
    atomic allocate(sz):
      result←remove(sz)
      if result = null
        //惰性清扫
        lazySweep(sz)
        result←remove(sz)
      //如果还为空,就会触发垃圾回收  
      return result
    //惰性清扫  
    lazySweep(sz):
      repeat
        //从惰性队列中获取合适的块
        block←nextBlock(reclaimList, sz)
        if block != null
          //清扫块
          sweep(start(block), end(block))
          if spaceFound(block)
            return
        until block = nu11
        allocS1ow(sz)
        
    //获取分块
    al1ocSlow(sz):
      block←allocateBlock()
      if block != null
        initialise(block,sz)
2,引用计数
  1. 简介

    引用计数算法指在对象头中的开辟一个空间叫计数器用于保存对象被引用的次数,如果引用次数为0则为垃圾对象,大于0则为存活对象。 简单的垃圾回收算法

    //创建对象
    New():
        //分配空间返回引用
        ref ← allocate()
        //如果不到空间报错
        if ref = nu11
            error "Out of memory"
        //将这个引用的计数设置为0
        rc(ref) <- 0
        //返回引用
        return ref
    /**
     *修改当前对象的引用计数
     *atomic 标记此方法为原子的,涉及到写屏障的干预,防止并发对ref的引用。src 源对象的指针,
     *i待修改的域的下标,ref i下标待设置的引用,
     *先添加再删除。当src[i] == ref时,先删除会导致src[i]处的引用被回收,即ref会为null。 
     */
    atomic Write(src, i, ref):
        //增加ref的引用计数
        addReference(ref)
        //删除src源对象i下标的引用
        deleteReference(src[i])
        src[i] ← ref
    addReference(ref):
        if ref≠nu11
            rc(ref) ← rc(ref) + 1
    deleteReference(ref):
        if ref≠null
            rc(ref) ← rc(ref) - 1
        /**
         *ref的引用计数为0时,需要将其子节点的引用-1,比如集合,当集合对象的引用计数为0时,
         *表示这个集合即将会被回收,这时集合内元素引用计数也有-1,否则会影响后续回收。
         */
        if rc(ref) = 0
        for each fld in Pointers(ref)
            deleteReference(*fld)
        //回收引用
        free(ref)
  1. 延迟引用计数

    引用计数算法必须是原子的且计数和指针必须是同步的,同时新老对象都会被更新会导致高速缓存中的数据被污染。比如当A对象引用B,现在A引用C,这时计数算法会先更新A的指针域,同时C的计数器+1,B的计数器-1。这一过程对于系统来说开销是比较大的。延迟引用计数就是将局部变量引用计数操作延迟处理来缓解频繁的计数更新,但是为了保证引用计数的正确性使用零引用表存储这些引用。

    image.png

    延迟引用计数算法

    //创建对象
    New():
        //分配空间
        ref ← allocate()
        //如果分配的空间引用为null
        if ref = nu11
            //回收内存空间
            collect()
            //再次分配
            ref ← allocate()
            //如果ref为null,报错
            if ref = nu11
            error "Out of memory"
        //修改引用计数为0
        rc(ref) ← 0
        add(zct, ref)
        return ref
    /**
     *修改当前对象的引用计数
     *src 源对象的指针,i待修改的域的下标,ref i下标待设置的引用
     */    
    Write(src, i, ref):
        //当src为根对象集合时,直接将ref放在src的i下标
        if src = Roots
            src[i] ← ref
        else
            //atomic 标记此方法为原子的,涉及到写屏障的干预,防止并发对ref的引用
            atomic
                //增加ref的引用计数
                addReference(ref)
                //从零引用表中(zct)移除ref
                remove(zct, ref)
                deleteReferenceToZCT(src[i])
                //将ref放在src的i下标
                src[i] ← ref
                
    //将引用计数-1,如果引用计数为0时需要将ref添加到零引用表中(zct),等待延迟释放           
    deleteReferenceToZCT(ref):
        if ref ≠ nu11
            rc(ref) ← rc(ref) 一1
            if rc(ref) = 0
                add(zct,ref)
    /**
     *atomic 标记此方法为原子的,涉及到写屏障的干预,防止并发
     *回收系统中的垃圾
     */
    atomic collect():
        //遍历被根引用的对象并对对象引用计数+1
        for each fld in Roots
            addReference(*f1d)
        //回收零引用列表    
        sweepZCT()
        //遍历根引用的对象,每个根引用对象调用deleteReferenceToZCT()方法
        for each fld in Roots
            deleteReferenceToZCT(*f1d)
            
    //遍历零引用列表,如果零引用列表中的引用为0则需要将其子节点的引用-1,回收内存空间        
    sweepZCT():
        while not isEmpty(zct)
            ref ← remove(zct)
            if rc(ref) = 0
                for each fld in Pointers(ref)
                    deleteReference(*fld)
            free(ref)

零引用表何时添加引用合适移除引用

  • 当对象创建且还没有被引用时,将此引用放到零引用列表中。
  • 当对象被引用时,需要检查对象是否在零引用列表中,如果在移除。
  • 当堆内存不足时,会遍历零引用列表,判断如果对象的引用计数为0时则移除,并回收。
  1. 合并引用计数

    合并引用计数是在延迟引用计数基础上进行优化的,许多引用计数操作都是临时性的、“不必要”的,通过在程序运行时仅跟踪对象在某一时段开始和结束时的状态,忽略中间过程引起的计数值变更,达到减少引用值频繁修改的目的。

    • 当对象的指针域被修改时,算法会将对象及对象的指针域的值都更新到本地缓冲区中,同时将该对象标记为脏。

    image.png

    • 当分配空间不足时,需要进行垃圾回收。先将本地缓冲区合并到回收日志中,然后遍历回收日志取出对象进行对象指针域引用的先加后减。如下图,当遍历到A时,遍历A的指针域,对A的指针域所有引用对象B D 的计数加1,然后根据回收日志中A的指针域B C进行减1。计数处理后将引用为0的对象放到零引用列表中。

    image.png 合并引用计数算法

    //当前线程id
    me ← myThreadId;
    write(src,i,ref):
        // 如果src不是脏,将src添加本地缓冲区,同时将src标记为脏
        if not dirty(src)
            log(src)
        //将引用ref写到src的i位置
        src[i] ← ref  
        
    //添加本地缓冲区
    log(obj):
        //遍历指针域,将指针域中的引用添加到本地日志
        for each fld in Pointers(obj)
            if *fld ≠ nu11
                append(updates[me], *f1d)
        //如果obj(src)不为脏时,将src添加到本地日志。       
        if not dirty(obj)
            slot ← appendAndCommit(updates[me], obj)
            //将obj(src)设为脏
            setDirty(obj, slot)
            
    //判断是否为脏        
    dirty(obj):
        return logPointer(obj) ≠CLEAN
        
    //设置脏    
    setDirty(obj, slot)
        logPointer(obj) ← slot
    
    //分配空间    
    atomic collect():
        //为每个线程分配缓冲区
        collectBuffers()
        //执行引用计数
        processReferenceCounts()
        //回收零引用列表
        sweepZCT()
    
    
    collectBuffers():
        //构建一个空的数组,用于充当回收器日志
        collectorLog ← []
        for each t in Threads
            /**
             *updates[t] 当前t线程的本地缓冲区,遍历每个线程,
             *将每个线程的本地缓冲区追加到回收器日志中
             */
            collectorLog ← collectorLog + updates[t]
            
    processReferenceCounts():
        //遍历日志收集器
        for each entry in collectorLog
            //获取entity的引用
            obj ← objFromLog(entry)
            //判断是否为脏,防止多个线程对一个对象重复处理
            if dirty(obj)
                //清空脏标记
                logPointer(obj) ← CLEAN
                //先将obj的引用的对象的引用计数+1
                incrementNew(obj)
                //再将obj的引用的对象的引用计数-1
                decrementold(entry)
                
    decrementOld(entry): 
        //遍历所有指针域
        for each fld in Pointers(entry)
            child <- *fld
            if child ≠ nu1l
                //为每个指针引用计数 -1
                rc(child)rc(child) 一1
                if rc(child) = 0
                    //如果引用为0,将其添加到零引用列表中
                    add(zct, child) 
    
    incrementNew(obj):
        //遍历所有指针域
        for each fld in Pointers(obj)
            child <- *fld
            if child != nu1l
                //为每个指针引用计数+1
                rc(child) <- rc(child) +1
  1. 环状引用计数

    如果是环状数据结果,其环内引用必定为1,因此引用计数算法无法回收这类数据结构。这种算法是在确定为环状垃圾范围下并消除了内部引用对引用计数的影响。最常见的就是双向链表。

    • 从备选垃圾开始为每个环内垃圾标记为灰色,并消除内部引用计数。如果引用计数不为0的则必然存在被环外对象引用,但是也不能将引用计数为0的对象视为垃圾。

    image.png

    • 从备选垃圾开始在环内扫描,如果引用计数不为0的则必然存在被环外对象引用,将引用计数不为0的对象标记为黑色,引用计数为0的标记为白色,但是不能将标记为白色的对象视为垃圾,因为这些白色对象可能会被黑色对象引用,所以需要扫描黑色对象的指针域根据实际情况进行颜色的修复。

    image.png

    • 回收环内白色对象

    环状引用计数算法

    //创建对象
    New():
        //分配空间
        ref <- allocate()
        if ref = null
            //使用环状引用计数回收器
            collect()
            ref <- allocate() 
            if ref = nu11
                error "Out of memory"
        rc(ref) <- 0
        return ref
        
    //增加ref的引用计数    
    addReference(ref):
        if ref != nu11
            rc(ref) <- rc(ref) + 1
            //将ref标记为黑色
            colour(ref) <- black
    //减少ref的引用计数
    deleteReference(ref):
        if ref != nu11
        rc(ref) <- rc(ref) -1
        if rc(ref) = 0
            //释放
            release(ref)
        else
            //可能是环状垃圾,任何一个引用计数大于1的对象都有可能是环状垃圾,将其添加到备选垃圾集合中去
            candidate(ref)
    
    //释放垃圾
    release(ref):
        //处理指针域的每个指针
        for each fld in Pointers(ref)
            deleteReference(fld)
        //将对象标记为黑色,空闲链表中的对象均为黑色    
        colour(ref) <- black
        //如果不是备选垃圾,直接释放对象
        if not ref in candidates
            free(ref)
            
    //将ref标记为备选垃圾,并将其添加到备选垃圾集合中        
    candidate(ref):
        //如果对象不是紫色(ref可能是环状垃圾的备选根),
        //将对象变为紫色,并将其添加到备选垃圾集合中
        if colour(ref) != purple
            colour(ref) <- purple
            candidates <- candidates U {ref}
            
    //环状引用计数回收器,atomic标记,此方法是线程安全的            
    atomic collect():
        //将备选根标记为灰色并消除环内引用
        markCandidates()
        //循环扫描每一个环状垃圾备选根
        for each ref in candidates
            scan(ref)
        //回收白色对象    
        collectCandidates()

    /**
     *遍历所有的环状垃圾的备选根,如果是紫色(该对象添加到candidates后没有被引用过),则标记为灰色。
     *否则就从环状垃圾的备选根集合移除,如果对象标记为黑色且引用计数为0则回收
     */
    markCandidates():
      for ref in candidates
        if colour(ref) = purple
          markGrey(ref)
        else 
          remove(candidates, ref)
          if colour(ref) = black & rc(ref) = 0
            free(ref)
            
    /**
     *如果不是灰色,则标记为灰色。
     *遍历指针域,将环内指针的引用计数-1(减去的这个1认为是环内对象的相互引用)
     */        
    markGrey(ref):
      if colour(ref) != grey
        colour(ref) <- grey
        for each fld in Pointers(ref)
          child <- *fld
          if child != nu1l
            rc(child) <- rc(child) -1
            markGrey(child) 
            
    /**
     *只扫描标记为灰色的ref
     *如果ref引用计数>0,则存在环外引用,标记为黑色
     *否则将ref标记为白色,循环处理指针域的每个引用
     */
    scan(ref):
      if colour(ref) = grey
        if rc(ref) > 0
          scanBlack(ref)
        else
          colour(ref) <- white
          for each fld in Pointers(ref)
            child <- *fld
            if child != null
              scan(child)  
              
    /**
     *将ref标记为黑色,遍历ref指针域,依次处理每个引用
     *将指针域的引用+1(环内的黑色对象引用了它),如果指针域引用不是黑色,继续扫描
     */
    scanBlack(ref):
      colour(ref) <- black
      for each fld in Pointers(ref)
        child <- *fld
        if child != nu11
          rc(child) <- rc(child) + 1
          if colour(child) != black
            scanBlack(child)
            
    //collectCandidates, collectWhite主要对标记白色的对象进行回收       
    collectCandidates():
      while not isEmpty(candidates)
        ref <- remove(candidates)
        collectWhite(ref)

    collectWhite(ref): 
      if colour(ref) = white && not ref in candidates
        colour(ref) <- black
        for each fld in Pointers(ref)
          child <- *fld
          if child != nul1
            collectWhite(chi1d)
        free(ref)        

附图

image.png

3,标记整理
  1. 简介

    标记清除算法,会存在堆中产生碎片,标记整理主要是对碎片的整理。

  2. 双指针整理算法

    双指针算法是采用任意顺序算法,需要遍历两次堆空间,第一次遍历的目的是整理内存,即移动对象; 第二次遍历的目的则是更新所有指向被移动对象的指针。

    算法的第一次遍历(图 1.1-1.5)

    • 在算法初始阶段,指针free指向区域开始,指针scan指向区域末尾,在第一次遍历过程中,指针free 不断向前移动,指针scan不断向后移动;

    • 指针free不断向前移动,直到遇到一个空闲位置,指针scan不断向后移动,直到遇到一个存活对象;

    • 当指针free和指针scan分别指向空闲位置和存活对象时,准备将 scan 指向的对象移动到 free 的 位置;

    • 将 scan 指向的对象移动到 free 的位置,scan 指向的位置记录下原对象移动到了哪里(图中为 B 位置),并将这块内存标记为空闲;

    • 当指针free和指针scan发生交错,遍历结束。

    算法的第二次遍历

    • 初始化时,指针scan指向区域的起始位置;

    • 然后开始遍历,如果指针scan指向的对象中包含指向空闲位置的指针 p,则 p 指向的内存块中必定 记录着对象移动后的地址,然后将 p 指向这个地址。比如,图中对象 3 有一个指针指向对象 4,对 象 4 移动后,其原来的内存块 F 中记录着其移动后的地址 B,那么需要将这个指针修改为指向 B;

    • 继续遍历,直到指针scan指向的位置为空闲内存,遍历结束。 image.png

    算法源码

    //回收
    atomic collect ():
        //标记
        markFromRootS()
        //整理
        compact()
        
    compact():
        relocate(HeapStart, HeapEnd)
        updateReferences(HeapStart,free)
    /**
     *第一次遍历,移动指针
     */
    relocate(start, end):
        free <- start
        scan <- end
        //在free < scan时,进行指针移动
        while free < scan
            //当free指向的区域被标记(空闲域)了,先解除标记,然后free向下一个指针域移动,
            //如果没有标记则停止在free位置,跳出循环体
            while isMarked(free)
                unsetMarked(free)
                free <- free + size(free)
            //当scan指向的位置没有标记(存活的对象)且scan > free,scan向前移动   
            while not isMarked(scan) && scan > free
                scan <- scan - size(scan)
            //两指针的位置进行交换    
            if scan > free
                unsetMarked(scan)
                move(scan, free)
                *scan <- free 
                free <- free + size(free)
                scan <- scan - size(scan)
    //第二次遍历,修改指针引用            
    updateReferences(start, end):
        //将根中引用了移动对象的引用进行修改
        for each fld in Roots
            ref <- *fld
            if ref ≥ end
                *fld <- *ref
        //将scan指向开始位置        
        scan <- start
        while scan< end
            //将存活对象中引用了移动对象的引用进行修改
            for each fld in Pointers(scan)
                ref <- *fld
                if ref >= end
                    *f1d <- *ref
            scan <- scan + size(scan)

  1. Lisp 2算法

    滑动回收算法,需要三次堆遍历,将存活对象按顺序依次移动到堆的另一端。

    第一次遍历

    • 1,在初始状态,指针free和指针scan都在起始位置

    • 2,指针scan向前遍历,直到遇到存活对象,然后将 free 指向的地址写入对象 1 的forwarding address域

    • 3,指针free和指针scan均向前移动 size(对象的大小) 步

    • 4,指针scan继续向前移动,直到下一个存活对象,然后重复步骤 2 和步骤 3,直到指针scan跑到 堆的末端

    image.png

    第二次遍历

    • 通过对象头的forwardingaddress域,更新 GC Roots 以及被标记对象的引用,该操作确保它们指向 对象的新位置

    image.png 第三次遍历

    • 将每个存活对象移动到其新的目标位置

    image.png Lisp 2算法源码

    compact():
        //第一次循环,指针scan向前遍历,直到遇到存活对象,然后将 free 指向的地址写入的
        //forwardingaddress域
        computeLocations(HeapStart, HeapEnd, HeapStart)
        //第一次循环,修改roots和存活对象引用
        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)
            
    updateReferences(start, end):
        for each fld in Roots
            ref <- *fld
            if ref != nu11
                *fld <- forwardingAddress(ref)
        scan <- start 
        while scan < end
            if isMarked( scan)
                for each fld in Pointers(scan)
                    if *fld != nu11
                        *fld <- forwardingAddress(*f1d)
            scan <- scan + size(scan)
            
    relocate(start, end):
        scan←start 
        while scan < < end
            if isMarked( scan)
                dest <- forwardingAddress(scan)
                    move(scan, dest)
                    unsetMarked(dest)
                scan <- scan + size(scan)

  1. 引用整理算法

    滑动回收算法,需要两次堆遍历。第一次遍历处理前向指针,并完成后面对象的引线,第二次遍历处理后向指针,并移动对象到新的位置。

    image.png

    1,A对象,B对象在堆中位于N的前面,也可以说相对于N,AB在N的低地址处。C对象在N的后面,也可以说相对 于N,C在N的高地址处。
    2,低地址指向高地址的指针为前向指针,高地址指向地地址的指针为后向指针。 3,A->N,B->N为N的前向指针,C->N为N的后向指针。

    第一次遍历,主要是对对象引用引线和对前向指针的处理

    • 堆的原始状态 image.png

    • 遍历到A,逆转A->N指针

      1,当遍历到A时,这时所有指向A的前向指针已处理引用引线,现在只需要将这些引用修改 到新的地址上,这里带过下,等到遍历到N时进行说明,即是算法update方法
      2,对A的指针域所有指针进行引线,也就是A->N这个指针也会进行引线。同时将N上头信息info移动到A 的头信息,即是算法thread方法 image.png

    • 忽略B(B和A一样),遍历到N时

      1,当遍历到N时,这时所有指向N的前向指针已处理引用引线,现在只需要将这些引用修改到 新的地址上,将A->N,B->N指针的指向进行修改到Addr上,然后将info恢复到N上,即是算法update 方法 。像C->N先不动,待遍历到C对象时处理。
      2,对N的指针域所有指针进行引线,即是算法thread方法。

      image.png image.png

    • 遍历到C时

      1,当遍历到C时,这时所有指向C的前向指针已处理引用引线,现在只需要将这些引用修改 到新的地址上, 即是算法update方法。
      2,对C的指针域所有指针进行引线,也就是C->N这个指针也会进行引线,即是算法thread方法。 这里N来说C在高地址,C->N为后向引用,N已经对所有指向自己的前向指针更新到了新的位置 Addr上,无法对C->N指针进行修改,故才有了第二次遍历的处理后向指针的操作。

      image.png

    第二次遍历,主要对后向指针的处理,并对象的移动

    • 第一次遍历后,堆的状态

    image.png

    • 遍历A

      1,当遍历到A时,先处理指向A的后向指针到新的地址上,这里带过下,等到遍历到N时进行 说明。 2,将A移动到A'处,算法的move方法。

    image.png

    • 遍历N

      1,当遍历到N时,先处理指向N的所有后向指针到新的地址上,比如C->N,将C->N指向修改到 Addr上,将info恢复到N上。 2,将N移动到Addr处,算法的move方法。

    image.png

    Jonker算法源码 注意:这是伪代码只解释意思

    compact():
        updateForwardReferences()
        updateBackwardReferences()
    /**
     *引线,以上图中的A来说明
     * ref:A,比如内存地址为0X123,主要也是逆转ref
     * *ref:从0X123中取出值,这里就是N的地址,比如是0X234
     * **ref:从0X123中取出值0X234,在从0X234取出值,这里就是指info值
     * *ref, **ref <- **ref, ref:用info值覆盖0X234的值,用0X123覆盖info值。这样就完成了ref      * 的逆转
     */
    thread(ref):
        if *ref != null
            *ref, **ref <- **ref, ref 
    
    /**
     * 将ref修改到新的地址addr上
     * 比如ref是N
     * 第一轮循环:tem = N,将新地址addr覆盖N的地址,将*tem的值即B赋值给tmp
     * 第二轮循环:tem = B,循环执行完成后,B->N指针的指向由B指向Addr
     * 第三轮循环:tem = A,循环执行完成后,A->N指针的指向由A指向Addr
     * 循环结束后将A中的info恢复到N
     */
    update(ref, addr):
        tmp = *ref
        while isReference(tmp)
            *tmp, tmp = addr, *tmp
        *ref = tmp
        
    //处理前向指针    
    updateForwardReferences():
        //对根的指针域进行引线
        for each field in Roots
            thread(*field)
 
        free = HeapStart
        scan = HeapStart
        //开始遍历堆
        while scan <= HeapEnd
            if isMarked(scan)
                //将引用自己的前向指针的指向修改为未来的地址
                update(scan, free)
                //对当前对象的指针域进行引线
                for each field in Pointers(scan)
                    thread(field)
                //向前移动free    
                free += size(scan)
            //向前移动scan       
            scan += size(scan) 
    //处理后向指针,并将对象移动到新位置        
    updateBackwardReferences():
        free = HeapStart
        scan = HeapStart
        while scan <= HeapEnd
            if isMarked(scan)
                //这个修改就是修改后向指针的指向
                update(scan, free)
                //移动
                move(scan, free)
                //向前移动free 
                free += size(scan)
            //向前移动scan    
            scan += size(scan)

  1. 单次遍历算法

    使用位图,每一位对应内存的一个字。标记过程中发现存活对象时,设置其第一个字和最后一个字对应位图中的位。回收器在整理阶段通过标记向量分析计算出每个对象的大小,最后结合偏移向量在一次堆遍历过程中完成对象的移动和指针的更新。

    image.png 单次遍历算法源码

    compact():
        computeLocations(HeapStart, HeapEnd, HeapStart)
        updateReferencesRelocate(HeapStart, HeapEnd)
    
    //位图标记后,根据标记向量计算偏移量
    computeLocations(start, end, toRegion):
        loc <- toRegion
        //获取start处的块
        block <- getBlockNum(start)
        //遍历位图
        for b <- 0 to numBits(start, end)-1
            //等于0时表示遍历到块的第一个位图,BITS_IN_BLOCK块中位图的长度
            if b % BITS_IN_BLOCK = 0
                //设置块block第一个存活对象的转发地址
                offset[block] <- loc
                //块++
                block <- block + 1
            //如果标记了    
            if bitmap[b] = MARKED
                //在当前转发地址下,增加存活对象的大小,作为下一个块的转发地址
                loc <- loc + BYTES_PER_BIT
                
    //更新指针,移动位置
    updateReferencesRelocate(start, end):
        //处理根指针域
        for each fld in Roots
            ref <- *fld
            if ref != nu11
                //获取新地址赋值给指针
                *fld <- newAddress(ref)
        scan <- start
        while scan < end
            scan <- nextMarked0bject (scan)
            for each fld in Pointers(scan)
                ref <- *fld
                if ref≠null
                    *fld <- newAddress(ref)
            //获取新地址赋值给指针        
            dest <- newAddress(scan)
            move(scan, dest)
    
    //获取新地址        
    newAddress(o1d):
        //获取块
        block <- getBlockNum(o1d)
        //offsetInBlock 里面存得是存活对象在块中的偏移量
        //块的第一个存活对象的转发地址+old对象在块的偏移量就是对象转发的地址
        //这个计算其实就是在转发地址向后移动offsetInBlock(old)
        return offset[block] + offsetInBlock(old)

4. 复制式回收算法
  1. 简介

    将堆二等分A B,每次垃圾回收时将存活对象从A移动到堆的另一端B,然后清空A。

  2. 半区复制回收

    将堆二等分成来源空间和目标空间。当需要执行垃圾回收时,将来源空间的存活对象移动到目标空间,但在存活对象移动之前,两个空间需要互换角色。在垃圾回收之后会将来源空间的所有对象进行回收。

    image.png

    image.png

    image.png

    image.png

    image.png 半区复制源码

    atomic collect():
        //翻转空间和目标空间调换角色
        flip()
        //初始化工作空间,其实就是将scan和free指向目标空间的起始位置
        initialise(worklist)
        //从根指针域开始复制
        for each fld in Roots
            process(fld)
        //遍历工作列表    
        while not isEmpty(worklist)
            //返回scan,并将scan向前移动到新的位置
            ref <- remove(worklist)
            //扫描scan处对象的指针域
            scan(ref)
            
    flip():
        fromspace,tospace <- tospace,fromspace
        top <- tospace + extent
        free <- tospace
        
    scan(ref):
        for each fld in Pointers(ref)
            process(f1d)
            
    process(fld):
        fromRef <- *fld
        if fromRef ≠nu1l
            //使用目标区域的新地址进行更新fld的值
            *fld <- forward(fromRef)
            
    forward(fromRef):
        toRef <- forwardingAddress(fromRef)
        if toRef = null
            toRef <- copy(fromRef)
        return toRef
        
    //将存活对象从来源区域复制到目标区域    
    copy(fromRef):
        toRef <- free
        free <- free + size(fromRef)
        move(fromRef, toRef)
        forwardingAddress(fromRef) <- toRef
        add(worklist,toRef)
        return toRef