Language agnostic 三色增量更新GC:是否需要扫描每个堆栈两次?

Language agnostic 三色增量更新GC:是否需要扫描每个堆栈两次?,language-agnostic,garbage-collection,Language Agnostic,Garbage Collection,让我给你一个三色GC的简短介绍(以防有人读到它却从未听说过它);如果你不在乎,跳过它,跳转到问题上来 三色GC的工作原理 在三色GC中,对象具有三种可能颜色中的一种;白色、灰色和黑色。三色GC可描述如下: 所有对象最初都是白色的 由于全局变量或堆栈变量引用而可访问的所有对象(“根对象”)均为灰色 我们取任何灰色的物体,找到它对白色物体的所有引用,并将这些白色物体涂成灰色。然后我们把物体本身涂成黑色 只要有灰色对象,我们就继续执行步骤3 如果我们不再有灰色对象,所有剩余的对象都是白色或黑色 所有的

让我给你一个三色GC的简短介绍(以防有人读到它却从未听说过它);如果你不在乎,跳过它,跳转到问题上来

三色GC的工作原理 在三色GC中,对象具有三种可能颜色中的一种;白色、灰色和黑色。三色GC可描述如下:

  • 所有对象最初都是白色的

  • 由于全局变量或堆栈变量引用而可访问的所有对象(“根对象”)均为灰色

  • 我们取任何灰色的物体,找到它对白色物体的所有引用,并将这些白色物体涂成灰色。然后我们把物体本身涂成黑色

  • 只要有灰色对象,我们就继续执行步骤3

  • 如果我们不再有灰色对象,所有剩余的对象都是白色或黑色

  • 所有的黑色物体都被证明是可以到达的,并且必须保持生命。所有白色对象都无法访问,可以删除

  • 到目前为止,这还不太复杂……至少如果GC是StW(停止世界),这意味着它将在收集垃圾时暂停所有线程。如果它是并发的,则三色GC具有一个必须始终为真的不变量:

    黑色对象不能指白色对象

    这对于StW GC自动适用,因为之前已经检查过每个黑色对象,并且它指向的所有白色对象都是灰色的,因此黑色对象可能仅指其他黑色对象或灰色对象

    如果线程没有暂停,线程可以执行破坏此不变量的代码。有几种方法可以防止这种情况:

  • 捕获对指针的所有读取访问,并查看是否对白色对象进行了此读取访问。如果是,请立即将该对象涂成灰色。如果现在将对该对象的引用指定给黑色对象,则无所谓,该对象将不再是白色而为灰色(此实现使用读取屏障)

  • 捕获对指针的所有写访问,并查看分配的对象是否为白色,而分配给它的对象是否为黑色。如果是,将白色对象涂成灰色。这是一种更明显的方法,但也需要更多的处理时间(此实现使用写屏障)

  • 由于读访问比写访问更为常见,即使第二种可能性在遇到障碍时涉及更多的处理时间,但调用它的频率较低,因此是最受欢迎的一种。这样工作的GC称为“增量更新GC”

    这两种技术都有一种替代方法,称为SatB(开始时的快照)。考虑到并非真的需要始终保持不变量这一事实,这种变化的工作原理略有不同,因为只要GC知道在当前GC循环中该白色对象过去是并且仍然是可访问的,那么黑色对象是否指白色对象并不重要(因为仍然有灰色对象引用此白色对象,或者因为此白色对象的a引用被放在显式堆栈上,GC在用完灰色对象时也会考虑该堆栈).SatB收集器在实践中使用得更多,因为它们有一些优点,但实际上更难实现

    这里我指的是增量更新GC,它使用变量2:每当代码试图使黑色对象指向白色对象时,它会立即将对象变为灰色。这样,在收集周期中不会丢失此对象

    问题 关于三色GCs有很多。但是有一件事我不明白。假设我们有一个对象A,它被堆栈引用,它本身引用一个对象B

    stack -> A.ref -> B
    
    现在GC开始一个循环,停止线程,扫描堆栈,并将a视为可直接访问,将其着色为灰色。一旦扫描完整个堆栈,它将再次取消暂停线程,并在步骤(3)开始处理。在开始执行任何操作之前,它将被抢占(可能发生)线程再次运行并执行以下代码:

    localRef = A.ref; // localRef points to B
    A.ref = NULL;     // Now only the stack points to B
    sleep(10000);     // Sleep for the whole GC cycle
    
    由于未违反不变量,B为白色,但未分配给黑色对象,B的颜色未改变,仍然为白色。a不再指B,因此在处理“灰色”时A,B不会改变颜色,A会变成黑色。在循环结束时,B仍然是白色,看起来像垃圾。但是,localRef指的是B,因此它不是垃圾

    问题 我说得对吗,三色GC必须扫描每个线程的堆栈两次?一开始扫描一次,以识别根对象(得到灰色)在删除白色对象之前,这些对象可能会被堆栈引用,即使没有其他对象再引用它们。到目前为止,我所看到的算法描述中没有提到任何关于扫描堆栈两次的内容。他们都只说,在并发使用时,始终强制执行不变量非常重要,否则会丢失可访问的对象。但就我所见,这还不够。堆栈必须被视为一个大对象,一旦扫描,“堆栈为黑色”,堆栈的每个引用更新都必须使对象变为灰色


    如果真是这样的话,使用增量更新可能比我最初认为的更棘手,并且有一些性能缺陷,因为堆栈更改是所有更改中最频繁的一个。

    一点术语:

    让我列举一些名称,以便更清楚地解释

    变量是数据的任何插槽,它可能包含指针,并且可能随时间而变化。这包括全局变量、局部变量、CPU寄存器和分配对象中的字段

    在三色增量或并发GC中,有三种类型的变量: