为什么在并发GC上需要注释阶段

时间:2015-04-24 11:14:11

标签: java concurrency garbage-collection

并发GC需要remark phaseremark phase的作用是在concurrent mark phase期间标记已修改的对象。但我认为 如果我们只在concurrent mark phase期间标记新创建的对象,则无需执行remark phase

由于修改了对象,因此需要

remark phase。修改可以是两种类型。一个是新对象创建,另一个是修改指向另一个对象的指针。如果我们标记新创建的对象,则可以轻松解决新对象问题。并且修改指向另一个对象的指针实际上不是问题。因为

  

死对象无法复活

死对象意味着没有人可以指向该对象。他们怎么能复活?因此修改后的指针应指向已标记的对象。这意味着无需执行remark

有人可以这么说,"在其创作上标记新对象太昂贵了。因此,在concurrent mark phase期间无法标记它们,这就是remark phase需要的原因"。这看起来很合理。但这可能会产生另一个问题。 remark如何不遍历根目录中的每个对象?如果remark phase遍历根目录中的每个对象,则concurrent mark phase完成的工作将毫无用处。或者,如果remark phase仅遍历已修改的对象,则应将某个对象的修改信息保存在某处。我认为这可能比标记要贵得多。

我错了吗?应该是错的。但我不知道哪一点是错的。

2 个答案:

答案 0 :(得分:3)

  

实际上,修改指向另一个对象的指针不是问题。因为

     
    

死对象无法复活

  

他们真的不能不知道哪些物品已经死了?没有!为什么呢?

初始标记阶段之后你不知道它,因为你只查看线程堆栈并且不遵循引用。

您不知道在并发标记阶段之后是否会发生以下情况:

  • 线程读取字段a.x并将其值存储在其寄存器中(或其堆栈或其他位置)。
  • 然后这个帖子设置a.x = null(或其他东西)。
  • GC来了,看到null
  • 然后线程将a.x恢复为之前的值。

现在,GC错过了对象a.x指向的对象。虽然上述情况并不常见,但可能会发生并且存在更现实(也更复杂)的情景。

因此有必要再次查看修改后的内存,即备注阶段。幸运的是,当card table被使用时,不能再次扫描整个内存。

我担心this (otherwise nice) explanation在这一点上有点误导:

  

评论阶段是一个停止世界。如果应用程序同时运行并且不断更改正在运行的内容,则CMS无法正确确定哪些对象处于活动状态(将其标记为实时)。

线程会改变什么是实时,但它们也会改变您可以看到的实时。这就是问题所在。

This article相当清楚地说明了这一点:

  

备注阶段的部分工作涉及重新扫描已被应用程序线程更改的对象(即,查看对象A以查看应用程序线程是否已更改A,以便A现在引用另一个对象B和B先前未被标记为现场。

我会说:当你一个接一个地搜索一个房间时,当孩子们移动它们时,你可能会错过你的眼镜。

关于场景的说明

我很确定,上面的情况是可能的,它并不完全是程序通常所做的。有关一个非常现实的例子,请考虑

void swap(Object[] a, int i, int j) {
    Object tmp = a[i];
    a[i] = a[j];
    // Now the original reference a[i] is in a register only.
    a[j] = tmp;
}

答案 1 :(得分:0)

GC必须始终查看自当前周期开始以来已修改的每个存储引用,因为可能在某个地方保存的GC尚未显示的引用将存储在它已查看和删除的位置从它原来的位置。并发GC可以在不停止世界的情况下重新访问修改后的引用,但在执行此操作时,其他线程可能会继续修改更多引用。

如果在GC进行全面扫描时10%的对象被修改,那么它可能值得在不停止世界的情况下访问10%,但是当它访问10%的其他线程时可能会干扰3%。同时访问3%可能是值得的,但其他线程可能会打扰2%。进一步的传递可能会减少在stop-the-world模式下需要完成的工作量,但是当其他线程仍处于干扰引用的过程中时,GC不太可能完全完成。除非所有线程都自发地停止使用引用做任何事情,否则GC将无法在不停止世界的情况下100%完成。

请注意,GC设计可能会承诺永远不会停止世界超过一定的时间,但可能会牺牲新的分配请求。 GC无法确切知道完成一个循环需要多长时间,因为在循环完成之前,它无法知道是否存在尚未发现的对大型集合的根的未被发现的引用。 - 未被发现的物体。另一方面,如果GC达到某个点 - 在没有这样的发现的情况下 - 它将期望在1ms内完成,然后让世界重新开始发现会导致它花费超过2毫秒。让世界重新启动将使得有必要在GC释放任何内存之前再次停止世界,并且可能很难保证没有事件组合可能导致无休止的“最终清理”尝试中止,但是如果允许的“停止世界时间”相对于代码对引用进行“搅拌”的数量是合理的,则最终清理失败应该是罕见的,并且异常重复。

相关问题