SATB开始时快照

SATB开始时快照

SATB开始时快照 #

解决活跃对象漏标的问题的两种常见的方法,分别是“往前走”和“往后退一步”。还有第三种解法,这是由日本学者汤浅太一提出的,具体的算法如下图所示: 在这张图中,我们可以看到,当对象 B 对 C 的引用关系消失以后,再将 C 标记为灰色,即便将来 A 对 C 的引用消失了,也会在当前 GC 周期内被视为活跃对象。也就是说,C 有可能变成浮动垃圾。我们把这种在删除引用的时候进行维护的屏障叫做 deletion barrier。

G1 中采用的就是这种做法。这种做法的特点是,在 GC 标记开始的一瞬间,活跃的对象无论在标记期间发生怎样的变化,都会被认为是活跃的对象。

我们知道,当一个对象的全部引用被删除时,才会被当做垃圾。而如果使用我们前面讲到的 deletion barrier,在并发标记阶段,即便对象的全部引用被删除,也会被当做活跃对象来处理。就好像在 GC 开始的瞬间,内存管理器为所有活跃对象做了一个快照一样,所以人们给了这种技术一个很形象的名字:开始时快照(Snapshot At The Beginning,SATB)。

你要注意的是,有些文章对 SATB 的解释是:在 GC 开始时将堆做一个内存快照,存放到磁盘上。这种说法就是望文生义了。因为快照这个词在计算机领域通常是指压缩,索引等技术,所以就有人把这里的快照理解成了对堆对象的一种压缩。由此,我们就知道这种错误的说法是怎么来的了。

当 B 对象对 C 对象的引用消失时,C 对象将会被标记为灰色。这个动作的效率是比较低的,如果都放在写屏障中做,会极大地影响程序性能。因为写屏障的逻辑是由业务线程执行的。

为了解决这个问题,GC 开发者将“C 对象标记为灰色”这件事情往后推迟了。业务线程只需要把 C 对象记录到一个本地队列中就可以了。每个业务线程都有一个这样的线程本地队列,它的名字是 SATB 队列。

当业务线程发现对象 C 的引用被删除之后,直接将 C 放到 SATB 队列中,并不去做标记,真正做标记的工作交给 GC 线程去做,这样就减少了写屏障的开销。

如上图所示,每个线程有自己的本地 SATB 队列,当本地队列满了之后,就把它交给 SATB 队列集合,然后再领取一个空队列当做线程的本地 SATB 队列。GC 线程则会将 SATB 队列集合中的对象标记为灰色,至于什么时候标记,并不需要业务线程关心。

Viewpoint #

From #

22 | G1 GC:分区回收算法说的是什么?