一个老程序员的分享:G1垃圾收集器之SATB
对象漏标
垃圾回收的并发标记阶段,gc线程和应用线程是并发执行的,所以一个对象被标记之后,应用线程可能篡改对象的引用关系,从而造成对象的漏标、误标,其实误标没什么关系,顶多造成浮动垃圾,在下次gc还是可以回收的,但是漏标的后果是致命的,把本应该存活的对象给回收了,从而影响的程序的正确性。
为了解决在并发标记过程中,存活对象漏标的情况,GC HandBook把对象分成三种颜色:
- 黑色:自身以及可达对象都已经被标记
- 灰色:自身被标记,可达对象还未标记
- 白色:还未被标记
所以,漏标的情况只会发生在白色对象中,且满足以下任意一个条件:
- 并发标记时,应用线程给一个黑色对象的引用类型字段赋值了该白色对象
- 并发标记时,应用线程删除所有灰色对象到该白色对象的引用
对于第一种情况,利用post-write barrier,记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍
对于第二种情况,利用pre-write barrier,将所有即将被删除的引用关系的旧引用记录下来,最后以这些旧引用为根重新扫描一遍
SATB
SATB全称snapshot-at-the-beginning,由Taiichi Yuasa为增量式标记清除垃圾收集器开发的一个算法,主要应用于垃圾收集的并发标记阶段,解决了CMS垃圾收集器重新标记阶段长时间STW的潜在风险。
Region包含了5个指针,分别是bottom、previous TAMS、next TAMS、top和end,
其中previous TAMS、next TAMS是前后两次发生并发标记时的位置,全称top-at-mark-start,
1、假设第n轮并发标记开始,将该Region当前的top指针赋值给next TAMS,在并发标记标记期间,分配的对象都在[next TAMS, top]之间,SATB能够确保这部分的对象都会被标记,默认都是存活的
2、当并发标记结束时,将next TAMS所在的地址赋值给previous TAMS,SATB给 [bottom, previous TAMS] 之间的对象创建一个快照Bitmap,所有垃圾对象能通过快照被识别出来
3、第n+1轮并发标记开始,过程和第n轮一样
SATB保证了在并发标记过程中新分配对象不会漏标但如果在TAMS之前有一个白色对象W,被一个灰色对象G引用,在并发标记扫描到这个字段之前被赋值为null,切断了对象W和对象G之间的引用关系,对象W就有可能漏标,这就是白色对象被漏标的第二种情况?
G1中如何解决?
在引用关系被修改之前,插入一层 pre-write barrier
pre-write barrier最终执行逻辑:
通过G1SATBCardTableModRefBS::enqueue(oop pre_val)把原引用保存到satb mark queue中,和RSet的实现类似,每个应用线程都自带一个satb mark queue.
在下一次的并发标记阶段,会依次处理satb mark queue中的对象,确保这部分对象在本轮GC是存活的。