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线程是并发执行的,因此可能产生新的对象或对象关系发生变化,例如:
- 新生代的对象晋升到老年代;
- 直接在老年代分配对象;
- 老年代对象的引用关系发生变更; 对于这些对象,需要重新标记以防止被遗漏。 为了提高重新标记的效率,本阶段会把这些发生变化的对象所在的Card标识为Dirty,这样后续就只需要扫描这些Dirty Card的对象,从而避免扫描整个老年代。
3.并发预清理(CMS-concurrent-preclean)
这个阶段就是用来处理:前一个阶段因为引用关系改变导致没有标记到的存活对象的,它会扫描所有标记为Dirty的Card 将会重新扫描前一个阶段标记的 Dirty 对象,并标记被 Dirty 对象直接或间接引用的对象,然后清除Card标识。 也就是让这个标记过后的对象,重新标记为存活。
3.1.可被终止的预清理(CMS-concurrent-abortable-preclean)
本阶段尽可能承担更多的并发预处理工作,从而减轻在Final Remark阶段的stop-the-world。 主要循环的做两件事:
- 处理 From 和 To 区的对象,标记可达的老年代对象;
- 和上一个阶段一样,扫描处理Dirty Card中的对象。 具体执行多久,取决于许多因素,满足其中一个条件将会中止运行:
1.执行循环次数达到了阈值; 2.执行时间达到了阈值;
4.重新标记(CMS Final Remark)
由于第三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次 被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。 这一阶段十分重要,因为必须避免收集到仍被引用的对象。
5.并发清除(CMS concurrent sweep)
所有不再被应用的对象将从堆里清除掉。
6.并发重置 (CMS-concurrent-reset)
状态等待下次CMS的触发 做一些收尾的工作,以便下一次GC周期能有一个干净的状态。这是与用户线程同时运行;
运行状态
了解了几个特点之后,可以实际的看看CMS的运行状态。 上面说到的CMS的几个状态,我们从gc运行日志中去查看CMS的运行状态。 日志中以 CMS 开头的都是CMS各个执行阶段。 这里最需要注意的是:
- CMS Initial Mark
- CMS-remark
这两个阶段分别会暂停应用,也是会让应用程序短暂的暂停也就是
STW
。
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缺点
这个不得不吐槽一下,这几个问题,还全让我碰到了。
- CMS收集器对CPU资源非常敏感
- CMS收集器无法处理浮动垃圾
- 浮动垃圾
CMS收集器对CPU资源非常敏感
在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。 这个的实际产生的问题就是,用户线程在处理业务时,由于停顿导致的停顿时间,刚好使业务线程处理时间超时,导致业务处理超时失败。
CMS收集器无法处理浮动垃圾
可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。 这个问题,是在检查服务性问题的时候发现的,频率一天几十次,最终调优解决。
浮动垃圾
由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现的标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”。 这个就是追求短的低停顿的代价,实际的问题是,内存空间会有大量的对象占用,如果是创建对象比较频繁的应用,就不太友好了,这样也会加大回收的频率。
CMS是一款“标记--清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
总结
CMS 是一款低可以实现短停顿的回收器,在停顿时间上确实是比较优秀的,一般来说没有最好的应用,只有最合适的应用,在选择GC时,根据自身的需求进行选择并对细节做过调整,来达到最优的效果。