G1的并发标记过程

G1的并发标记过程

G1的并发标记过程 #

因为 Evacuation 发生的时机是不确定的,在并发标记阶段也可能发生。所以并发标记要使用一个 BitMap 来记录活跃对象,而 Evacuation 也需要使用一个 BitMap 来将活跃的对象进行搬移。这就产生了读和写的冲突:并发标记需要写 BitMap,而 Evacuation 需要读 BitMap。

为了解决这个问题,G1 维护了两个 BitMap,一个名为 nextBitMap,一个名为 prevBitMap。其中,prevBitMap 是用于搬移活跃对象,而 nextBitMap 则用于并发标记记录活跃对象。

当并发标记开始以后,新的对象仍然有可能会被继续分配。内存管理器把这些对象全部认为是活跃对象。TAMS 指针,是 Top At Mark Start 的缩写。

这是初始时状态,prevTAMS,nextTAMS 和 top 指针都指向一个分区的开始位置。 随着业务线程的执行,top 指针不断向后移动。并发标记开始时,nextTAMS 记录下当前的 top 指针,并且针对 nextTAMS 之前的对象进行活跃性扫描,扫描的结果就存放在 nextBitMap 中。

当并发标记结束以后,nextTAMS 的值就记录在 prevTAMS 中,并且 nextBitMap 也赋值给 prevBitMap。 如果此时发生了 Evacuation,则 prevBitMap 已经可用了。如果没有发生 Evacuation,那么 nextBitMap 就会清空,为下一轮并发标记做准备。这样就可以保证,在任意时刻开启 Evacuation 的话,prevBitMap总是可用的。

在并发标记开始以后,再创建的对象,其实就是 nextTAMS 指针到 top 指针之间的对象,这些对象全部认为是活跃的(注意观察图中紫色部分)。

我们再从对象活跃性的角度理解两个 TAMS 指针和 top 的关系。当并发标记开始时,nextTAMS 就固定了,但是 top 还是可能继续向后移,所以 nextTAMS 和 top 之间的对象在这次标记过程中都被认为是活跃对象。当 Evacuation 开始时,它只使用 prevBitMap 的信息,显然 prevBitMap 中的信息只能覆盖到 prevTAMS 处,所以从 prevTAMS 到 top 的对象就都认为是活跃的。

top 指针是一个 Region 内已分配区域和未分配区域的界限。通过 TAMS 和 BitMap,GC 线程可以清楚地知道一个 Region 内活跃对象的分布,不仅可以确定 Evacation 的范围,还可以用来计算一个 Region 的垃圾比例,为 CSet 选择提供参考。

Viewpoint #

From #

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