G1算法中的记忆集用于什么?

4

我刚刚读了一些有关G1算法的博客。

我对记忆集的使用感到困惑。

这是我的理解:

既然我们可以使用DFS遍历从GC-Roots引用的每个引用,那么为什么我们需要记忆集?

因为所有的博客都说使用记忆集的原因是我们不需要检查每个区域,以查看是否有一个对象被GC-Roots引用。


由于在代际 GC 中,我们可能需要在年轻一代中查找所有活动对象而不遍历较旧的一代... - Michael
这是否意味着每次g1想要回收一个区域时,它都需要检查它的记忆集以及记忆集中的每个指针,如果最终到达一个gc-roots,那么就意味着它是不可回收的? - ratsafalig
1
@Michael,你说得对,但是记忆集在G1成为分代垃圾收集器之前就已经“诞生”了。事实上,这些集合的大小触发了G1成为分代垃圾收集器。 - Eugene
1
@ratsafalig,你的理解是错误的。引用并不会被遍历到GC根节点。引用指向的方向相反,因此只能从根节点遍历到对象。否则,垃圾回收就不会是如此昂贵的操作。 - Holger
1个回答

10
首先你需要了解什么是 "Card Table"。如果从老生代回到年轻代引用了某个对象,你如何只扫描并清理年轻代区域呢?为了避免破坏堆,你需要“跟踪”这些连接的确切位置,在扫描年轻代时,可以清理它们。想一想:如果现在一个对象A在年轻代中,但是有一个来自老生代的引用B指向它,那么你不能标记A以待删除。因此需要实现一个 "Card Table" 来跟踪这些连接。这个卡表的每个位表示老生代的某个部分是“脏”的,也就是说,在扫描年轻代时,也要扫描这个部分。
为什么需要这样做呢?扫描年轻代的整个目的是扫描堆的一小部分,而不是全部。这个 "Card Table" 实现了这一点。
G1具有多个区域。如果正在扫描regionA,并且您发现它指向其他regionB,则仅将此信息放入Card Table是不够的。您的 Card Table 将仅知道 regionA,下次扫描 regionB 时,如何知道您应该同时扫描 regionA?如果不这样做,显然会破坏堆的完整性。因此,有个 "Remembered Sets"。这些集合由异步线程填充:它扫描 Card Table 并根据该信息扫描这些 "脏" 区域的指针所在的位置。它跟踪 regionA->regionB 这种连接。每个区域都有自己的 "Remembered Set"。
因此,在需要进行 GC 的时候,当扫描 regionB 时,您还会查看其 "Remembered Set" 并找出您还需要扫描 regionA。

实际上,这就是为什么G1成为了一种分代式垃圾收集器:这些已记忆集的大小非常庞大。如果将堆划分为年轻代老年代,则无需保留年轻代之间的连接,因为您无论如何会一次性扫描它们,从而减少这些集合的大小。 G1希望保持那个200毫秒(默认值)的承诺-为此,您需要一次性扫描年轻代(因为在已记忆集中没有区域之间的连接,否则堆完整性将丢失),但与此同时,如果使年轻代较小,则已记忆集的大小将会很大。

因此,调整这些设置是一项工程奇迹,个人认为。


1
我可以这样认为吗:记忆集卡表的更精确版本,因为卡表仅知道哪个区域,而记忆集则知道确切地址。 - ratsafalig
说实话,它们不是彼此的“版本”。G1拥有它们两个。 - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接