Introduction to CMS - Concurrent Mark Sweep Collector

Introduction

The Concurrent Mark Sweep Collector (“CMS Collector”) of the HotSpot JVM has one primary goal: low application pause times.

CMS被设计成在大多数时间能与应用程序线程并行执行,仅仅会有一点(短暂的)停顿时间。GC与应用程序并行的缺点是可能会出现各种同步和数据不一致的问题。为了实现安全且正确的并发执行,CMS收集器的GC周期被分为了好几个连续的阶段。


Theory

CMS收集器的GC周期由6个阶段组成。其中4个阶段(名字以Concurrent开始的)与实际的应用程序是并发执行的,而其他2个阶段需要暂停应用程序线程。

CMS收集器的过程:

  1. 初始标记(STW):为了收集应用程序的对象引用需要暂停应用程序线程,该阶段完成后,应用程序线程再次启动。
  2. 并发标记:从第一阶段收集到的对象引用开始,遍历所有其他的对象引用。
  3. 并发预清理:改变当运行第二阶段时,由应用程序线程产生的对象引用,以更新第二阶段的结果。
  4. 重标记:由于第三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。这一阶段十分重要,因为必须避免收集到仍被引用的对象。
  5. 并发清理(STW):所有不再被应用的对象将从堆里清除掉。
  6. 并发重置:收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态。

一个常见的误解是,CMS收集器运行是完全与应用程序并发的。事实并非如此,即使“stop-the-world”阶段相对于并发阶段的时间很短。

应该指出,尽管CMS收集器为老年代垃圾回收提供了几乎完全并发的解决方案,然而年轻代仍然通过“stop-the-world”方法来进行收集。对于交互式应用,停顿也是可接受的,背后的原理是年轻带的垃圾回收时间通常是相当短的。

在使用CMS时候,需要解决两个问题:

  • 堆碎片

    CMS收集器并没有任何碎片整理的机制,因此,应用程序有可能出现这样的情形,即使总的堆大小远没有耗尽,但却不能分配对象——仅仅是因为没有足够连续的空间完全容纳对象。

  • 对象分配率高

    如果创建对象实例的频率高于收集器清除堆里死对象的频率,并发算法将失败。从某种程度上说,老年代将没有足够的可用空间来容纳一个从年轻代提升过来的对象。这种情况被称为“并发模式失败”,并且JVM会执行堆碎片整理:触发Full GC。


Young Generation Garbage Collection

为什么调整新生代的参数会对应用的性能如此重要?

单纯从JVM的功能考虑,并不需要新生代,完全可以针对整个堆进行操作。新生代存在的唯一理由是优化垃圾回收(GC)的性能。更具体说,把堆划分为新生代和老年代的好处:

  • 简化了新对象的分配(只在新生代分配内存)
  • 可以更有效的清除不再需要的对象(即死对象)(新生代和老年代使用不同的GC算法)

通过广泛研究面向对象实现的应用,发现一个共同特点:很多对象的生存时间都很短。同时研究发现,新生对象很少引用生存时间长的对象。

结合这两个个特点,很明显GC会频繁访问新生对象,例如在堆中一个单独的区域,称之为新生代。在新生代中,GC可以快速标记回收”死对象”,而不需要扫描整个Heap中的存活一段时间的”老对象”。

HotSpot JVM又把新生代进一步划分为3个区域:一个相对大点的区域,称为”Eden”;两个相对小点的区域称为”From survivor”和”To survivor”。按照规定,新对象会首先分配在Eden中(如果新对象过大,会直接分配在老年代中)。在GC中,Eden中没有被回收的对象会被移动到survivor中,直至对象满足一定的年纪(定义为熬过GC的次数),会被移动到老年代。

基于大多数新生对象都会在GC中被收回的假设。新生代的GC使用复制算法。在GC前To survivor保持清空,当前的对象保存在Eden和From survivor中。GC运行时,Eden中的幸存对象被复制到To survivor。复制阶段完成后,Eden 和From survivor中只保存死对象,可以直接回收清空。如果在复制过程中To survivor被填满了,剩余的对象会被复制到老年代中。最后From survivor和 To survivor会调换下名字,在下次GC时,To survivor会变为From survivor。

对于From survivor中的幸存对象,会考虑对象年龄,如果年龄没达到阀值(tenuring threshold,15 GC cycles),对象会被复制到To survivor。如果达到阀值对象被复制到老年代。

总结一下,对象一般出生在Eden区,年轻代GC过程中,对象在两个survivor之间移动,如果对象存活到适当的年龄,会被移动到老年代。当对象在老年代死亡时,就需要更高级别的GC,更重量级的GC算法(复制算法不适用于老年代,因为没有多余的空间用于复制)


Reference

https://blog.codecentric.de/en/2013/10/useful-jvm-flags-part-7-cms-collector/
http://ifeve.com/useful-jvm-flags-part-7-cms-collector/
https://blog.codecentric.de/en/2012/08/useful-jvm-flags-part-5-young-generation-garbage-collection/