不会暂停的垃圾回收器,C4
更新日期:
不会暂停的垃圾回收器,C4 The Continuously Concurrent Compacting Collector,by Zing JVM @azulsystems
最近在看GC有很多不错的文章,顺便翻一下C4吧,似乎很神的样子
垃圾收集器相关
G1是Oracle下一代垃圾回收器,CMS的替代者一个不错的中文介绍
C4是Azulsystems的一篇论文,该公司提供了一个不会stop-the-world的zing JVM,知乎上大名鼎鼎的 @RednaxelaFX 就在这个公司。这是唯一找到的一篇C4中文介绍
隔行如隔山,这个C4的论文实在是看不懂,还好我找到了RednaxelaFX大人的blog。如果你也读不懂,我建议先先去看看
简要翻译
其中很多参考了 @RednaxelaFX的内容以及他的,blog,表示感谢:)
以及R大的一些讨论
http://hllvm.group.iteye.com/group/topic/44381#post-272188
http://hllvm.group.iteye.com/group/topic/21468
Write Barrier
这篇论文讲的非常清楚,讲了很多实现,说的也很清楚:一篇关于Write Barrier以及Store Buffer的论文
write barrier 是在实现部分垃圾收集(partial GC)时用于记录从非收集部分指向收集部分的指针的集合的结构。
分代式GC是一种部分垃圾收集的实现方式。当分两代时,通常把这两代叫做young gen和old gen;通常能单独收集的只是young gen。此时remembered set记录的就是从old gen指向young gen的跨代指针。
Write Barrier实现方式1:Remembered Set && 卡表(card table)
1 | 所以说 Remembered Set 和卡表应该都算是Write Barrier的一种实现方式,当然,卡表还算是Remembered Set的一个特例。 |
- 粒度问题
所谓粒度问题,就是每个指向yong代的指针到底代表多大的一块空间。所以无论是remembered set还是card table,记录精度都有很大的选择余地:
- 字粒度:每个记录精确到一个机器字(word)。该字包含有跨代指针。
- 对象粒度:每个记录精确到一个对象。该对象里有字段含有跨代指针。
- card粒度:每个记录精确到一大块内存区域。该区域内有对象含有跨代指针。
- 还有其它可能性,任君想像
但一般而言,有一些隐含假设,当提到Remembered Set,一般指的是对象粒度,而卡表一般值一个内存块。
- 实现数据结构
- 对于地址空间较大的情况,可以考虑直接使用指针,也就是一般意义上的Remembered Set
1 | struct RememberedSet { |
或者
1 | typedef char* address; |
- Card Table 则是Remembered Set一种特殊实现,用每个bit隐式代表一块内存区域,所以会格外省空间
1 | struct CardTable { |
- 简要实现方式猜测
这块目前还没看论文,我猜测应该是在新生代生成新对象的时候,查看是否有指向这些新生代的老年代对象,从而更改Remembered Set。
疑问1: 那么在做Full GC时,是不是也要从新清理一遍整个Remember Set呢?
Write Barrier实现方式2:Store Buffer
还有一个与remembered set相关的概念,叫做store buffer。由于其实现方式也被称为“sequential store buffer(SSB)”。
有些资料会把store buffer也看作remembered set的一种实现,但我喜欢把前者看作与后者相关/近似的概念,而不是“实现方式”。例如最老的V8使用per-page remembered set,而比较新的版本使用store buffer。
(使用remembered set的V8,以最早的V8 0.1为例,每个“Page”有8KB,其中开头有248字节用于remembered set(RSet)。RSet里每个bit对应该Page里的一个word,所以这是word精度的。而使用store buffer的V8也是word精度的。)
两者的相似之处在于它们都记录跨区域的指针。
而最重要的区别是:remembered set是一个集合(set),所以不包含重复;store buffer则通常允许包含重复。Store buffer的write-barrier比要去重复的remembered set的writer-barrier要简单和高效,但由于其允许重复,前者在部分收集(例如young GC)时的开销会比后者大。
一个折衷的办法是在mutator的write-barrier还是允许重复,然后周期性增量式或在另一个线程并发的对store buffer去重。这样到实际执行部分收集时重复条目的数量可以大幅减少,提高GC的效率。V8的store buffer就是这样做的。
这种还需要对数据做后续处理的write-barrier也叫做logging write-barrier。
从F大的论述中可以才
mutator
感觉是内存分配器?