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

Jof*_*rey 8 java concurrency garbage-collection

并发GC需求remark phase.其作用remark 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仅遍历已修改的对象,则应将某个对象的修改信息保存在某处.我认为它可能比标记要昂贵得多.

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

maa*_*nus 3

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

死亡物体无法复活

他们确实不能,但你知道哪些物体是死的吗?不!为什么?

在初始标记阶段之后您不知道它,因为您只查看线程堆栈而不跟踪引用。

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

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

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

所以需要再次查看修改后的内存,也就是remark阶段。幸运的是,由于使用了卡表,因此不必再次扫描整个内存。


恐怕这个(否则很好的)解释在这一点上有点误导:

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

线程确实改变了实时的内容,但它们也改变了您所看到的实时的内容。这就是问题所在。

这篇文章说得比较清楚:

重新标记阶段的部分工作涉及重新扫描已被应用程序线程更改的对象(即,查看对象 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;
}
Run Code Online (Sandbox Code Playgroud)