SATB的一些理解
啃G1的时候,SATB(Snapshot At The Beginning)这个术语看我的很是迷糊。简单解释是:“GC开始时对象关联的快照”,但这个解释……貌似有点歧义。查阅了不少资料,也全都一笔带过不解释
下面结合一些前置的垃圾回收知识,总结一下我对SATB的理解
前置知识
增量式垃圾回收
增量(incremental)式垃圾回收,是指GC和Mutator交替运行的一种垃圾回收工作方式,如下图所示
Hotsopt中的G1就是一款增量式的垃圾回收器,老一代的CMS也有增量模式。在增量式垃圾回收下,可以降低Mutator的暂停时间,只是吞吐量没那么高(只是说增量回收下,并不是指G1)
三色标记法
三色标记法(Tri-color marking)是Edsger W. Dijkstra 等人提出的,在增量式垃圾回收里非常有用,用于标记GC过程中不同阶段的对象的状态。
- 白色:还未搜索过的对象
- 灰色:正在搜索的对象
黑色:搜索完成的对象
GC 开始运行前所有的对象都是白色。GC 一开始运行,所有从根能到达的对象都会被标记,然后被堆到栈里。GC 只是发现了这样的对象,但还没有搜索完它们,所以这些对象就 成了灰色对象。
灰色对象会被依次从栈中取出,其子对象也会被涂成灰色。当其所有的子对象都被涂成 灰色时,对象就会被涂成黑色。 当 GC 结束时已经不存在灰色对象了,活动对象全部为黑色,垃圾则为白色。 这就是三色标记算法的概念。
有一点需要我们注意,那就是为了表现黑色对象和灰色对象,不一定要在对象头里设置标志(事实上也有通过标志来表现黑色对象和灰色对象的情况)。 在这里我们根据对象的情况,更抽象地把对象用三个颜色表现出来。每个对象是什么样的状 况,意味着什么颜色,这些都根据算法的不同而不同。
比如在增量式的标记-清除(Mark-Sweep)算法中,可以分为以下3个阶段:
- 根查找阶段
- 标记阶段
- 清除阶段
在根查找阶段,把所有能从根直接引用的对象标记为灰色。在标记阶段查找灰色对象,将其引用的对象也标记为灰色,查找结束(tracing完成)后将灰色对象标记为黑色。在最后的清除阶段,将黑色对象再标记为白色
标记遗漏
如果在标记阶段执行一半暂停后,Mutator更新了对象的引用关系,就可能会导致活动对象(reachable objects)的“标记遗漏”,一旦发生了标记遗漏,就可能会在最后的清除阶段造成误回收活动对象的严重问题
比如下面这个场景,在标记过程中暂停之后,Mutator修改了引用关系:
(a)是刚暂停的状态,A被标记为黑色,B被标记为灰色,接下来会对B进行遍历。此时继续执行Mutator
在(b)中,Mutator将A->B的引用,修改为A->C,然后删除了B->C的引用关系,本来是A->B->C,变成了A-C,就成了(c)图的情况
这个时候如果进行重新标记阶段就会出现问题:B本来是灰色对象,经过遍历后就被标记为了黑色。虽然此时C是活动对象,但也不会进行搜索了,因为没有一个灰色对象关联它,所以C就不会被标记为活动对象;那么在最后的清除阶段时,就会造成误清除。这种情况就成为标记遗漏
在上面这个例子里,造成标记遗漏的关键原因是,B->C的引用被移除了
写入屏障
写入屏障(Write Barrier)在GC里并不是一个具体的算法,只是一个“抽象”,其作用是在对象引用更新时增加一个“屏障”,在屏障中做一些增强操作。
下面是一段Edsger W. Dijkstra 等人提出的写入屏障实现(伪代码),在更新对象之间的引用时,需要调用write_barrier函数,从而实现“屏障”的功能。
write_barrier(obj, field, newobj){
if(newobj.mark == FALSE)
//标记新对象
newobj.mark = TRUE
//将新对象记录至标记栈
push(newobj, $mark_stack)
*field = newobj
}
在write_barrier函数中,更新引用的同时,将新对象标记后再记录至标记栈里,这样的话在待会的清除阶段,发生引用变化的对象活动对象也会被正常的标记了
通过这个写入屏障的方式,就可以解决上面的标记遗漏问题,因为记录了新的引用对象,新的引用对象也会被标记为活动对象,就不会出现标记遗漏了
汤浅太一 的算法
汤浅太一 在1990年开发了另一种增量垃圾回收算法,使用汤浅太一的写入屏障算法的GC也称为“Snapshot GC”。
这是因为这种算法是以 GC 开始时对象间的引用关系(snapshot)为基础来执行 GC 的。因此,根据汤浅的算法,在 GC 开始时回收垃圾,保留 GC 开始时的活动对象和 GC 执行过程中被分配的对象。
刚看这段时有点乱,SATB/Snapshot GC/Write Barrier几个概念有点混淆了
这里说的汤浅太一的算法,指的是它提出的一种实时垃圾回收的技术,和Edsger W. Dijkstra 等人提出不同。
“GC 开始时对象间的引用关系(snapshot)”这个也并不是说在标记阶段前,再新增一个快照的流程去记录引用关系。
这个算法的设计理念是“Snapshot”数据,它认为“在标记阶段中新的从根引用的对象在 GC 开始时应该会被别的对象所引用”(别的对象引用这里由写入屏障来记录)
所以这种基于初始引用关系作为基准数据,忽略了GC过程中的变化(新的从根引用的对象)这种方式,称为Snapshot
**
下面是汤浅太一 的写入屏障实现的伪代码,和上面介绍的写入屏障不同,在这个版本里,标记并添加到标记栈的对象变成了oldobj
write_barrier(obj, field, newobj){
oldobj = *field
if(gc_phase == GC_MARK && oldobj.mark == FALSE)
//标记老对象
oldobj.mark = TRUE
//将老对象记录至标记栈
push(oldobj, $mark_stack)
*field = newobj
}
通过写入屏障,记录引用变化前的对象(关系),从而构成“快照”。在汤浅的算法中,上面的ABC引用例子过程是这样:
在B->C的引用删除后,将C(oldobj)也标记为灰色,这样在标记阶段时,C还是会被遍历,这样就避免了标记遗漏的问题。
不过还是会有可能在并发标记过程中,某些对象已经没有引用不可达了,但是仍然会被标记,少回收几个也没什么关系……
G1 GC中的SATB
Hotspot G1 GC中的SATB,是汤浅太一写屏障算法的增强版,其核心还是基于快照理念“在标记阶段中新的从根引用的对象在 GC 开始时应该会被别的对象所引用”和写屏障来完成完整的标记
参考
- Taiichi Yuasa, _Real-time garbage collection on general-purpose machines_, Journal of Systems and Software, v.11 n.3, p.181-198, Mar. 1990
- 《垃圾回收的算法与实现》 中村成洋 , 相川光 , 竹内郁雄 (作者) 丁灵 (译者)
- 《深入Java虚拟机:JVM G1GC的算法与实现》中村成洋 (作者) 吴炎昌 , 杨文轩 (译者)
- HotSpot VM 请教G1算法的原理 - 资料 - 高级语言虚拟机 - ITeye群组
PDF 那些事
空无赞 5阅读 2.2k
谈JVM xmx, xms等内存相关参数合理性设置
京东云开发者赞 1阅读 348
GC 时间过长的问题处理思考
三流阅读 2.4k
谈JVM参数GC线程数ParallelGCThreads合理性设置
京东云开发者赞 3阅读 354
Redis分布式锁正确打开方式
京东云开发者赞 2阅读 306
有了HotSpot JVM为什么还需要OpenJ9?
骑牛上青山赞 2阅读 563
jvm垃圾回收机制
Smile3k阅读 951
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。