【问题标题】:Why does CMS collector collect root references from young generation on Initial Mark phase?为什么 CMS 收集器会在 Initial Mark 阶段从年轻代收集根引用?
【发布时间】:2013-12-20 17:34:53
【问题描述】:

据我所知,CMS 收集器收集老年代,它与 ParNew 收集器(用于收集年轻代)结合使用。 清楚地了解 CMS 的工作原理对我来说并不容易,但我是这样看的:

1) 初始标记。 寻找根参考。由于收集器是 oldgen 收集器,它应该只扫描 old generation。

2) 并发标记 找到所有根引用后,就该开始并发标记了。从第一阶段标记的对象可传递到达的所有对象都在该阶段进行标记。

3) 并发预清理 gc 查看 CMS 堆中的对象,这些对象在我们在前一个并发标记阶段进行并发标记时由年轻代的提升或新分配更新或由突变器更新。 [请确认 1 本阶段的唯一目的是完成下阶段必须完成的部分工作(备注)? 2) 有一些进程正在查看在并发标记阶段更改了哪些引用。 请告诉我这两点我是否正确]

4) 备注 gc 停止世界,然后查看 CMS 堆中的对象,这些对象通过年轻一代的提升或新分配进行更新,或者在我们执行并发预清理时由 mutators 更新。

但是今天看到这篇文章

初始标记 在初始标记期间,CMS 应收集所有根 开始标记旧空间的参考。这包括: 参考 来自线程堆栈,来自年轻空间的引用。参考文献 堆栈的收集通常非常快(不到 1 毫秒),但时间 从年轻空间收集引用取决于对象的大小 年轻空间。 通常初始标记在年轻空间之后开始 集合,所以伊甸园空间是空的,只有活的对象在其中之一 幸存者空间。幸存者空间通常很小且初始标记后 年轻的空间收集通常需要不到毫秒的时间。但如果 初始标记在伊甸园满时开始,可能需要很长时间 (通常比年轻空间收集本身长)。一次CMS 触发收集,JVM 可能会等待一些时间来进行年轻收集 在它开始初始标记之前发生。 JVM 配置 选项 –XX:CMSWaitDuration= 可用于设置 CMS 将等待多长时间 用于初始标记开始之前的年轻空间收集。如果你 想要避免长时间的初始标记暂停,你应该配置这个 时间比你的年轻收藏的典型时期长 应用。

备注大部分标记是与应用程序并行完成的,但它 可能不准确,因为应用程序可能会在期间修改对象图 标记。并发标记完成时;垃圾收集器应该 停止应用程序并重复标记以确保所有可达 标记为活动的对象。但是收集器不必遍历 通过整个对象图;它应该只遍历修改过的参考 自打标开始(实际上自开始预清洁阶段以来)。卡片 表(见卡片标记写屏障)用于识别修改 旧空间中的部分内存,但线程堆栈和年轻空间 应再次扫描。 通常备注阶段的大部分时间是 花费了扫描年轻空间。如果我们 在开始评论之前收集年轻空间中的垃圾。我们可以 指示 JVM 在 CMS 备注之前始终强制进行年轻空间收集。 使用 JVM 参数 –XX:+CMSScavengeBeforeRemark 启用此选项。 即使是年轻的空间是空的,评论阶段仍然需要扫描 修改旧空间中的引用,这通常需要时间接近 正常的年轻收集暂停(由于扫描期间完成的旧空间 young collection 类似于remark所需的扫描)。

http://blog.griddynamics.com/2011/06/understanding-gc-pauses-in-jvm-hotspots_02.html

不明白为什么 CMS 需要扫描年轻代。为什么老年代垃圾回收需要它?

【问题讨论】:

  • 它扫描从 YG 到 OG 的 references。从 CMS 的角度来看,它们是 GC 根。这并不意味着包含这些引用的对象对 CMS 很重要。

标签: java garbage-collection concurrent-mark-sweep


【解决方案1】:

您可能有具有循环引用的类,例如类 A 具有对 B 类的引用,而 B 具有对 A 的反向引用。如果您有属于这些类的对象 a 和 b,并且相互引用,则 gc当您从“外部”删除对它们的最后引用时,必须删除它们。当然,对于包含更多元素的参考循环,情​​况可能会复杂得多。因此 gc 必须检查哪些元素可以从某个根目录访问,哪些元素被引用但无法访问,应该被收集。

现在,如果你有,在你的代码中的某个地方

Object a=new A(new B(new C(new D())))

构造函数可能需要一些时间a 被分配。但是你不希望 gc 删除新创建的 D,只是因为 C 的构造函数需要一段时间才能运行,而 a 还没有被分配。因此,您还需要扫描年轻代,以捕获太年轻而无法从堆中引用的对象。

【讨论】:

【解决方案2】:

Java 堆被分成两个独立收集的部分:老空间和年轻空间。

要收集任一空间,您需要找到空间外的所有入站引用。它们是:

  • 堆栈中的局部变量
  • 嵌入到 JIT 编译代码 blob 中的文字
  • 来自另一个空间的所有引用

老集合应该扫描年轻空间和年轻集合应该扫描老空间没有区别。

卡表写屏障用于不扫描每个年轻集合的整个旧空间(只有一小部分旧空间包含指向年轻空间的链接,写屏障有助于跟踪该区域)。

但是young space没有card table,所以old collection应该扫描整个内存范围。

PS我是你引用的文章的作者,你可以在my blog找到更多与GC相关的文章

【讨论】:

猜你喜欢
  • 2016-07-20
  • 2019-06-24
  • 1970-01-01
  • 2017-06-02
  • 1970-01-01
  • 2015-07-30
  • 2023-03-07
  • 1970-01-01
  • 2021-12-10
相关资源
最近更新 更多