CMS垃圾收集器——重新标记和浮动垃圾的思考

《深入理解java虚拟机 第二版 JVM高级特性与最佳实践》里面提到 CMS 垃圾收集器。

CMS 垃圾收集器的垃圾回收分4个步骤:

  • 初始标记(initial mark) 有 STW
  • 并发标记(concurrent mark) 没有 STW
  • 重新标记(remark) 有 STW
  • 并发清除(concurrent sweep) 没有 STW

初始标记:仅仅标记 GC Roots 能直接关联到的对象。
并发标记:对初始标记标记过的对象,进行 trace(进行追踪,得到所有关联的对象,进行标记)
重新标记:(原文):为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。

CMS 垃圾收集器主要有三个问题:

  1. 内存碎片(原因是采用了标记-清除算法)
  2. 对 CPU 资源敏感(原因是并发时和用户线程一起抢占 CPU)
  3. 浮动垃圾:在并发标记阶段产生了新垃圾不会被及时回收,而是只能等到下一次GC

然后我产生了一个疑问:既然重新标记可以修正并发标记阶段的变动,那么为何还有浮动垃圾问题?

网上并没有找到有人对这个问题进行讨论,于是我在 Oracle 官方对 CMS 的介绍中找到了这样一句话:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html

The second pause comes at the end of the concurrent tracing phase and finds objects that were missed by the concurrent tracing due to updates by the application threads of references in an object after the CMS collector had finished tracing that object. This second pause is referred to as the remark pause.

翻译:第二次暂停是在并发跟踪阶段结束时进行的,它查找由于CMS收集器完成对对象的引用后,应用程序线程对对象中的引用进行更新而导致并发跟踪遗漏的对象。该第二暂停称为重新标记暂停。

以下是我个人对这个问题的解答猜想:

由于标记阶段是从 GC Roots 开始标记可达对象,那么在并发标记阶段可能产生两种变动:

  1. 本来可达的对象,变得不可达了
  2. 本来不可达的内存,变得可达了

第一种变动会产生所谓的浮动垃圾,第二种变动怎么回事呢?重点在于miss

如果并发标记阶段用户线程里 new 了一个对象,而它在初始标记和并发标记中是不会能够从 GC Roots 可达的,也就是were missed。如果没有重新标记阶段来将这个对象标记为可达,那么它会在清理阶段被回收,这是严重的错误,是必须要在重新标记阶段来处理的,所以这就是重新标记阶段实际上的任务。

相比之下,浮动垃圾是可容忍的问题,而不是错误。那么为什么重新标记阶段不处理第一种变动呢?也许是由可达变为不可达这样的变化需要重新从 GC Roots 开始遍历,相当于再完成一次初始标记和并发标记的工作,这样不仅前两个阶段变成多余的,浪费了开销浪费,还会大大增加重新标记阶段的开销,所带来的暂停时间是追求低延迟的CMS所不能容忍的。

你可能感兴趣的:(CMS垃圾收集器——重新标记和浮动垃圾的思考)