《A Generational Mostly-concurrent Garbage Collector》部分翻译和笔记

来源:互联网 发布:软件接口api 编辑:程序博客网 时间:2024/05/18 19:43

本文主要记录了学习一片E文的内容,关于CMS算法的知识,翻译一部分算法,记录这篇文章涉及到的其他名词,这些名词体现的内容是有意义的。

      首先列出这篇文章涉及到的一些可以引申的概念,以备以后查看

      (1)GC Interface 和 generational framework :

      (2)tricolor collection

      (3)write barrier 解决old-to-young的问题,virtual memory protection techniques 也是解决此类问题的方案 reference update tracking,

 

      开始翻译第三节

      3 Mostly-concurrent Collection

      原始的并发收集算法是由H. Boehm提出,在Conference on Programming Lan-guage Design and Implementation, pages 157-164,是并发的三色收集器(参考《On-the-fly garbage collection: An exercise in cooperation》),它使用“写栅栏”触发堆里面对象的域的更新,将域所在的对象置灰(对象的某个域指向了新的对象,就把这个对象置灰)。这篇文章的主要创新点在于提高了并发的吞吐量,根引用(全局、栈、寄存器)的更新比堆引用要更频繁被修改,根引用可以不通过栅栏维护三色不变量的方式进行修改。算法使用暂停应用线程的方式处理根,但是只花费很短的时间。算法共四个步骤,详述如下:

      初始标记 Initial marking pause :挂起所有的工作线程,记录所有的由根可直接触及的对象。

      并发标记阶段 Concurrent marking phase:回复工作线程。同时启动并发标记阶段,标记通过引用的传递可以达的所有对象。并发标记结束的时候,有一些可达对象可能没有被标记,因为并发的工作线程更新了对象的域,新指向的活跃对象就可能没有被标记,后面会有详细解释。为了解决这个问题,算法需要跟踪堆中对象的更行的那些域,这是唯一收集器和工作线程交互的地方。

      最终标记 Final marking pause:再次挂起工作线程,从根节点开始标记,这次把并行标记阶段的那些更新过的域也作为根。因为只有这些域含有并发阶段没有标记到的引用,就保证了在final marking 结束的时候,最终的标记结果包含所有可达对象。

            最终标记结果也可能会将一些曾经标记为可达的对象重新标记为不可达的,这些对象将在下一个垃圾回收周期被处理。例如后面的对象c。

      并发清扫阶段 concurrent sweeping phrase:回复工作线程,并发清扫堆,释放没有标记的对象。一定要注意别清扫新创建的对象,可以通过把新建对象标记为“live”的方式避免。

 

      该算法假设堆中对象包含引用对象的那些域很少发生变化(域指向新的对象),这时最终标记几点将会重新扫描许多dirty 的 包含引用的域,导致很长的甚至是破坏性的暂停。

     

3.1 一个具体的例子

      上图通过一个简单的例子描述了算法。在这个例子中,堆包括7个对象,分散在4个页中。

      在初始阶段(没有画),4个页被标记为clean,对象a标记为live,因为a可以从线程栈直接可达。

      图a描述了并发标记阶段执行到一半的情景。对象b,c,e已经被标记,这时工作线程执行了两个更新:g丢弃了对d的引用,b将原来对c的引用修改为指向d。图b描述了更新的结果,请注意第1页和第3页被标记为dirty。

      图c描述了并发标记结束时的情况。显然标记没有完全结束,一个标记过的对象(b)指向了一个没有标记过的对象(d)。这就需要第三个阶段的最终标记:所有的处于dirty页的标记过的对象(dirty页还有些为标记的/不可达的对象,例如c)被重新扫描。这将导致b被重新扫描,结果d就被标记。图d显示了最终标记的结果,这时标记完全结束了。紧随其后,并发清扫回收没有标记的对象f。

      和f同类的在GC周期的开始(initial mark phase)的没有被标记对象保证能被回收。但是,例如c,在initial mark阶段被标记了,但是在同一个周期后面的阶段又变成不可达的对象,在这个GC周期不一定能被回收,但是在下一个周期一定可以回收。

      第四节(1月21号)

      4.1 内存分配器

      Rearch VM的默认配置是年轻代采用拷贝收集,年老代使用标记-清扫收集,压缩使得后续可以有效分配内存,将这种收集的实现称作标记-压缩。在压缩的时候重新分配对象空间需要修改对象的引用,并发操作对象引用的更新是困难的。因此,mostly-concurrent 收集不移动对象,而是使用free lists,空闲链表根据对象大小分段,空闲链表的每个节点存放小对象(不大于100个4-byte的字),使用一组节点存放大对象(这些分组的大小类似于斐波那契序列)。注意到,大多数工作线程在年轻代为对象分配空间,而在年轻代采用拷贝收集,所以效率线性增长。只有很少的小对象,在年轻代年龄过大才晋升到年老代,涉及到效率较低的内存分配。

 

      4.2 使用卡片表Card Table

      分代的垃圾收集需要处理年老代指向年轻代的对象引用。因为年轻代的有些对象只被年老代的对象所引用,所以必须要考虑这个问题。通过遍历整个年老代可以解决这个问题,但是这将使年轻代的GC就和整个堆的GC一样了,我们需要一个更高效的方案。

      已经存在一些方案处理old-to-young引用的问题,在代价和准确性之间做不同的权衡。分代框架使用card table跟踪old-to-young引用。card table是一个数组,每一个条目对应JVM堆的一块称作card的区域。当工作线程对对象的引用字段的每次更新,执行一次write barrier,把含有引用字段的内存卡片对应的卡片表的条目设置为dirty。在编译工作线程代码的时候,为更新卡片表而附加的代码很有效:使用2-指令的write barrier 《A Fast Write Barrier for Generational Garbage Collectors.》

      年老代使用mostly-concurrent collection才用了card-table-based write barrier,由于这种方法有高效而且已经应用在年轻代的GC中,所以不会再增加而外负担。

原创粉丝点击