CMS(Concurrent Mark Sweep)

目标:获取最短回收停顿时间为目标的收集器。
算法:"标记-清除"算法实现

CMS是老年代垃圾收集器,在收集过程中可以与用户线程并发操作。
它可以与 Serial 收集器 和 Parallel New收集器搭配使用。
CMS牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。
可以通过JVM启动参数,来开启CMS:

-XX:+UseConcMarkSweepGC

牺牲吞吐量,追求收集速度是什么意思

其实实际使用过程中发现,CMS是将每次收集的时间减少,但是垃圾还是那么多,于是回收的工作方式就变成了跟吃自助餐常听到的一样"勤拿少取",就是每次回收时间短,也并不完全回收全部的垃圾,通过多次回来处理。

执行流程

1.初始标记(CMS initial mark)

单线程,标记新生代可达老年代的对象。
为了收集应用程序的对象引用需要暂停应用程序线程,该阶段完成后,应用程序线程再次启动。

2.并发标记(CMS-concurrent-mark)

在第一个阶段(Initial Mark)被暂停的应用线程将恢复运行。
通过遍历第一个阶段(Initial Mark)标记出来的存活对象,继续递归遍历老年代,并标记可直接或间接到达的所有老年代存活对象。

这个过程可能存在的问题
应用线程和GC线程是并发执行的,因此可能产生新的对象或对象关系发生变化,例如:

  1. 新生代的对象晋升到老年代;
  2. 直接在老年代分配对象;
  3. 老年代对象的引用关系发生变更;
    对于这些对象,需要重新标记以防止被遗漏。
    为了提高重新标记的效率,本阶段会把这些发生变化的对象所在的Card标识为Dirty,这样后续就只需要扫描这些Dirty Card的对象,从而避免扫描整个老年代。

3.并发预清理(CMS-concurrent-preclean)

这个阶段就是用来处理:前一个阶段因为引用关系改变导致没有标记到的存活对象的,它会扫描所有标记为Dirty的Card
将会重新扫描前一个阶段标记的 Dirty 对象,并标记被 Dirty 对象直接或间接引用的对象,然后清除Card标识。
也就是让这个标记过后的对象,重新标记为存活。

3.1.可被终止的预清理(CMS-concurrent-abortable-preclean)

本阶段尽可能承担更多的并发预处理工作,从而减轻在Final Remark阶段的stop-the-world。
主要循环的做两件事:

  1. 处理 From 和 To 区的对象,标记可达的老年代对象;
  2. 和上一个阶段一样,扫描处理Dirty Card中的对象。
    具体执行多久,取决于许多因素,满足其中一个条件将会中止运行:

1.执行循环次数达到了阈值;
2.执行时间达到了阈值;

4.重新标记(CMS Final Remark)

由于第三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次
被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。
这一阶段十分重要,因为必须避免收集到仍被引用的对象。

5.并发清除(CMS concurrent sweep)

所有不再被应用的对象将从堆里清除掉。

6.并发重置 (CMS-concurrent-reset)

状态等待下次CMS的触发
做一些收尾的工作,以便下一次GC周期能有一个干净的状态。这是与用户线程同时运行;

运行状态

了解了几个特点之后,可以实际的看看CMS的运行状态。
上面说到的CMS的几个状态,我们从gc运行日志中去查看CMS的运行状态。
日志中以 CMS 开头的都是CMS各个执行阶段。
这里最需要注意的是:

  1. CMS Initial Mark
  2. CMS-remark
    这两个阶段分别会暂停应用,也是会让应用程序短暂的暂停也就是STW
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2022-01-11T05:48:34.975+0800: 1774622.038: [GC (Allocation Failure) 2022-01-11T05:48:34.975+0800: 1774622.038: [ParNew: 2570680K->58383K(2831168K), 0.0411295 secs] 8357286K->5848239K(9122624K), 0.0414169 secs] [Times: user=0.2
7 sys=0.04, real=0.04 secs]
2022-01-11T05:48:35.030+0800: 1774622.093: [GC (CMS Initial Mark) [1 CMS-initial-mark: 5789855K(6291456K)] 5873624K(9122624K), 0.0093763 secs] [Times: user=0.05 sys=0.01, real=0.01 secs]
2022-01-11T05:48:35.039+0800: 1774622.103: [CMS-concurrent-mark-start]
2022-01-11T05:48:38.826+0800: 1774625.889: [CMS-concurrent-mark: 3.785/3.786 secs] [Times: user=8.65 sys=0.18, real=3.79 secs]
2022-01-11T05:48:38.826+0800: 1774625.889: [CMS-concurrent-preclean-start]
2022-01-11T05:48:39.400+0800: 1774626.463: [CMS-concurrent-preclean: 0.572/0.574 secs] [Times: user=0.64 sys=0.14, real=0.57 secs]
2022-01-11T05:48:39.400+0800: 1774626.463: [CMS-concurrent-abortable-preclean-start]
CMS: abort preclean due to time 2022-01-11T05:48:44.575+0800: 1774631.638: [CMS-concurrent-abortable-preclean: 5.169/5.175 secs] [Times: user=8.49 sys=0.52, real=5.18 secs]
2022-01-11T05:48:44.576+0800: 1774631.640: [GC (CMS Final Remark) [YG occupancy: 1078481 K (2831168 K)]2022-01-11T05:48:44.577+0800: 1774631.640: [Rescan (parallel) , 0.1331046 secs]2022-01-11T05:48:44.710+0800: 1774631.773: [
weak refs processing, 0.1347760 secs]2022-01-11T05:48:44.844+0800: 1774631.908: [class unloading, 0.0376418 secs]2022-01-11T05:48:44.882+0800: 1774631.945: [scrub symbol table, 0.0092684 secs]2022-01-11T05:48:44.891+0800: 1774
631.955: [scrub string table, 0.0012191 secs][1 CMS-remark: 5789855K(6291456K)] 6868337K(9122624K), 0.3817515 secs] [Times: user=1.12 sys=0.18, real=0.38 secs]
2022-01-11T05:48:44.959+0800: 1774632.022: [CMS-concurrent-sweep-start]
2022-01-11T05:48:50.297+0800: 1774637.361: [CMS-concurrent-sweep: 5.333/5.339 secs] [Times: user=10.01 sys=1.01, real=5.34 secs]
2022-01-11T05:48:50.298+0800: 1774637.361: [CMS-concurrent-reset-start]
2022-01-11T05:48:50.312+0800: 1774637.375: [CMS-concurrent-reset: 0.014/0.014 secs] [Times: user=0.03 sys=0.01, real=0.02 secs]
2022-01-11T05:48:57.315+0800: 1774644.378: [GC (Allocation Failure) 2022-01-11T05:48:57.315+0800: 1774644.378: [ParNew: 2574991K->57569K(2831168K), 0.1454595 secs] 4314737K->1800606K(9122624K), 0.1457231 secs] [Times: user=0.9
6 sys=0.17, real=0.14 secs]

CMS缺点

这个不得不吐槽一下,这几个问题,还全让我碰到了。

  1. CMS收集器对CPU资源非常敏感
  2. CMS收集器无法处理浮动垃圾
  3. 浮动垃圾

CMS收集器对CPU资源非常敏感

在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
这个的实际产生的问题就是,用户线程在处理业务时,由于停顿导致的停顿时间,刚好使业务线程处理时间超时,导致业务处理超时失败。

CMS收集器无法处理浮动垃圾

可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。
这个问题,是在检查服务性问题的时候发现的,频率一天几十次,最终调优解决。

浮动垃圾

由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现的标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”。
这个就是追求短的低停顿的代价,实际的问题是,内存空间会有大量的对象占用,如果是创建对象比较频繁的应用,就不太友好了,这样也会加大回收的频率。

CMS是一款“标记--清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

总结

CMS 是一款低可以实现短停顿的回收器,在停顿时间上确实是比较优秀的,一般来说没有最好的应用,只有最合适的应用,在选择GC时,根据自身的需求进行选择并对细节做过调整,来达到最优的效果。