CMS收集器的全称是"Mostly Concurrent Mark and Sweep Garbage Collector",它是老年代的收集器,算法是mark-sweep,它以避免老年代的长时间停顿为设计目标。与之搭配的新生代收集器是ParNew。

要启用CMS收集器需要在命令行中添加参数:

1-XX:+UseConcMarkSweepGC

相应地完整CMS GC日志如下:

 1$ java -Xms20m -Xmx20m -Xmn10m -verbose:gc -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC  messageSenderTest.JVMTest3
 2[GC (Allocation Failure) [ParNew: 7129K->567K(9216K), 0.0023311 secs] 7129K->6713K(19456K), 0.0023683 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
 3111
 4[GC (Allocation Failure) [ParNew: 6871K->6871K(9216K), 0.0000087 secs][CMS: 6146K->8192K(10240K), 0.0036427 secs] 13018K->12833K(19456K), [Metaspace: 2667K->2667K(1056768K)], 0.0036790 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
 5[GC (CMS Initial Mark) [1 CMS-initial-mark: 8192K(10240K)] 14881K(19456K), 0.0004250 secs] [Times: user=0.00 sys=0.02, real=0.00 secs]
 6[CMS-concurrent-mark-start]
 7[CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
 8[CMS-concurrent-preclean-start]
 9[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
10[CMS-concurrent-abortable-preclean-start]
11[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
12[GC (CMS Final Remark) [YG occupancy: 6853 K (9216 K)][Rescan (parallel) , 0.0003002 secs][weak refs processing, 0.0000035 secs][class unloading, 0.0002043 secs][scrub symbol table, 0.0003711 secs][scrub string table, 0.0001533 secs][1 CMS-remark: 8192K(10240K)] 15045K(19456K), 0.0011070 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
13[CMS-concurrent-sweep-start]
14[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
15[CMS-concurrent-reset-start]
16[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
17Heap
18 par new generation   total 9216K, used 6853K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
19  eden space 8192K,  83% used [0x00000000fec00000, 0x00000000ff2b15d8, 0x00000000ff400000)
20  from space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
21  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
22 concurrent mark-sweep generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
23 Metaspace       used 2673K, capacity 4486K, committed 4864K, reserved 1056768K
24  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K

CMS一共包括7个阶段,下面逐一分解。

Phase 1: Initial Mark

这是CMS中两个"stop-the-world"的其中之一。这个阶段的目标是标记老年代中这2种对象:a.直接被GC Roots引用 b.被新生代中存活的对象引用

cms_phase_01.png

1[GC (CMS Initial Mark) [1 CMS-initial-mark: 8192K(10240K)] 14881K(19456K), 0.0002765 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Phase 2: Concurrent Mark

在这个阶段中,垃圾收集器遍历老年代,从前一阶段"Initial Mark"中标记的roots开始标记所有活着的对象。在并发标记阶段,GC并发地和应用程序运行着,不会停止应用程序线程。需要注意老年代中并非所有活着的对象会被标记,因为在标记过程中应用程序会不断变更对象的引用。

cms_phase_02.png

在上面插图中,由"Current object"所指向的引用已经被标记线程移除掉。

1[CMS-concurrent-mark-start]
2[CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Phase 3: Concurrent Preclean

这是又一个并发阶段,与应用程序线程并发执行。在前一个"Concurrent Mark"过程中,一些对象引用已发生了改变,这时JVM会标记发生变化的对象所在的堆区域(称为"Card")。发生变化的对象称为dirty,这个过程称为Card Marking。

cms_phase_03.png

在pre-cleaning阶段,这些dirty对象会被记录,从这些dirty对象开始的可达的对象也会被标记。标记完成之后Card区域将被清除掉。

cms_phase_04.png

另外,为Final Remark阶段做一些必要的清理和准备工作也会在这个阶段执行。

1[CMS-concurrent-preclean-start]
2[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Phase 4: Concurrent Abortable Preclean

又是一个不会让应用线程停下来的并发阶段。这个阶段尝试尽可能多做工作为后面的"Final Remark"阶段减轻负担,该阶段的时间取决于很多条件,直到其中一个中断条件满足时才会终止。

1[CMS-concurrent-abortable-preclean-start]
2[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Phase 5: Final Remark

这是第二个stop-the-world阶段,停顿的目标是最终标记老年代中所有活着的对象。前面几个预清理(preclean)阶段是并发的,无法跟上应用程序的变更速度。这次的stop-the-world停顿要求完成历经煎熬的标记过程。

通常CMS在新生代比较空的时候执行Final Remark,这样做的好处是减少连续两次发生stop-the-world的可能性。这个阶段看起来比前面几个阶段都要复杂。

1[GC (CMS Final Remark) [YG occupancy: 6853 K (9216 K)][Rescan (parallel) , 0.0002730 secs][weak refs processing, 0.0000032 secs]
2[class unloading, 0.0001511 secs][scrub symbol table, 0.0003031 secs][scrub string table, 0.0001097 secs][1 CMS-remark: 8192K(10240K)] 15045K(19456K)
3, 0.0008706 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

在上面五个标记阶段之后,老年代中所有活着的对象都已经被标记好了,现在GC将清除老年代以回收无用的对象。

Phase 6: Concurrent Sweep

该阶段与应用程序并发执行,无需stop the world。这个阶段的目标是移除那些未被使用的对象,回收他们占据的空间。

cms_phase_05.png

1[CMS-concurrent-sweep-start]
2[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

Phase 7: Concurrent Reset

这个阶段是并发执行,重置CMS算法的内部数据结构,为下一次循环做准备。

1[CMS-concurrent-reset-start]
2[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

综上,CMS收集器在减少停顿时间上做了大量的作业,这是通过将很多工作转交给不要求用户线程停顿的并发GC线程来实现的。然而CMS也有很多缺点,最显著的就是老年代内存碎片,以及停顿时间是不可预测的,尤其是在堆比较大的时候。

Reference

本文参考翻译自 plumbr concurrent-mark-and-sweep