CMS GC学习笔记

阿凡达2018-08-21 09:32

CMS GC 算法

CMS GC算法相对于其他算法来讲要复杂很多,主要过程如下:

  1. 第一步初始化标记(initial mark) 比较简单。这一步骤只是查找那些距离类加载器最近的幸存对象。因此,停顿的时间非常短暂。
  2. 在之后的并行标记( concurrent mark )步骤,所有被幸存对象引用的对象会被确认是否已经被追踪和校验。这一步的不同之处在于,在标记的过程中,其他的线程依然在执行。
  3. 在重新标记(remark)步骤,会再次检查那些在并行标记步骤中增加或者删除的与幸存对象引用的对象。
  4. 最后,在并行交换( concurrent sweep )步骤,转交垃圾回收过程处理。垃圾回收工作会在其他线程的执行过程中展开。一旦采取了这种GC类型,由GC导致的暂停时间会极其短暂。

CMS GC也被称为低延迟GC。它经常被用在那些对于响应时间要求十分苛刻的应用之上。 当然,这种GC类型在拥有stop-the-world时间很短的优点的同时,也有如下缺点:

  • 它会比其他GC类型占用更多的内存和CPU
  • 默认情况下不支持压缩步骤

与CMS GC相关的参数

-XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用 CMS+串行收集器。
-XX:+ParallelCMSThreads: 设定 CMS 的线程数量。
-XX:+CMSInitiatingOccupancyFraction:设置 CMS 收集器在老年代空间被使用多少后触发,默认为 68%。
-XX:+UseFullGCsBeforeCompaction:设定进行多少次 CMS 垃圾回收后,进行一次内存压缩。
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收。
-XX:+CMSParallelRemarkEndable:启用并行重标记。
-XX:CMSInitatingPermOccupancyFraction:当永久区占用率达到这一百分比后,启动 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
-XX:UseCMSInitatingOccupancyOnly:表示只在到达阈值的时候,才进行 CMS 回收。
-XX:+CMSIncrementalMode:使用增量模式,比较适合单 CPU。
-XX:+UseCMSCompactAtFullCollectionFULL GC的时候,对年老代进行压缩,CMS是不会移动内存的,会产生碎片造成内存不够,增加这个参数是个好习惯,可能会影响性能,但是可以消除碎片

CMS GC日志分析

2016-06-26T02:16:33.814+0800: 225000.127: [GC [1 CMS-initial-mark: 4109808K(4194304K)] 4249081K(5033216K), 0.1662490 secs] [Times: user=0.16 sys=0.00, real=0.16 secs] 
初始标记:会暂停应用,单线程只标记根能直接可达的对象,很快。
2016-06-26T02:16:33.981+0800: 225000.293: [CMS-concurrent-mark-start]
2016-06-26T02:16:34.411+0800: 225000.723: [CMS-concurrent-mark: 0.430/0.430 secs] [Times: user=0.64 sys=0.04, real=0.43 secs]
并发标记:不会暂停应用
2016-06-26T02:16:34.411+0800: 225000.724: [CMS-concurrent-preclean-start]
2016-06-26T02:16:34.471+0800: 225000.783: [CMS-concurrent-preclean: 0.058/0.060 secs] [Times: user=0.08 sys=0.01, real=0.06 secs] 
预清理:不会暂停应用
该阶段检查并发标记阶段时从新生代晋升的对象,或新分配的对象,或被应用程序线程更新过的对象,帮助减少重新标记阶段的暂停时间。
2016-06-26T02:16:34.471+0800: 225000.784: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 2016-06-26T02:16:39.872+0800: 225006.184: [CMS-concurrent-abortable-preclean: 5.397/5.400 secs] [Times: user=7.19 sys=0.55, real=5.40 secs] 
可终止的预清理:不会暂停应用
继续预清理,至到Eden区占用量达到CMSScheduleRemarkEdenPenetration(默认50%),或达到5秒钟。
2016-06-26T02:16:39.872+0800: 225006.185: [GC[YG occupancy: 196297 K (838912 K)]
2016-06-26T02:16:39.872+0800: 225006.185: [Rescan (parallel) , 0.0875580 secs]
2016-06-26T02:16:39.960+0800: 225006.272: [weak refs processing, 0.0000670 secs]
2016-06-26T02:16:39.960+0800: 225006.272: [class unloading, 0.0052810 secs]
2016-06-26T02:16:39.965+0800: 225006.278: [scrub symbol table, 0.0025170 secs]
2016-06-26T02:16:39.968+0800: 225006.280: [scrub string table, 0.0005470 secs] [1 CMS-remark: 4109808K(4194304K)] 4306106K(5033216K), 0.0975850 secs] [Times: user=0.24 sys=0.01, real=0.10 secs] 
重新标记:会暂停应用,多线程,通过ParallelGCThreads指定,此阶段是CMS中暂停时间最长的。
重新从根扫描新生代中剩余的更新过的对象,也会处理其引用对象。
2016-06-26T02:16:39.970+0800: 225006.283: [CMS-concurrent-sweep-start]
2016-06-26T02:16:40.361+0800: 225006.674: [CMS-concurrent-sweep: 0.391/0.391 secs] [Times: user=0.55 sys=0.03, real=0.39 secs] 
并发清理:不会暂停应用
2016-06-26T02:16:40.361+0800: 225006.674: [CMS-concurrent-reset-start]
2016-06-26T02:16:40.373+0800: 225006.685: [CMS-concurrent-reset: 0.011/0.011 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 
重置:cms数据结构重新初始化,为下一次cms准备。

CMS GC中的两个failure以及优化点

Concurrent Mode Failure

出现此现象的原因主要有两个:

  1. 一个是在年老代被用完之前不能完成对无引用对象的回收
  2. 一个是当新空间分配请求在年老代的剩余空间中得不到满足

if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfied with the available free space blocks in the tenured generation, then the application is paused and the collection is completed with all the application threads stopped
  1. 方法一是降低触发CMS的阀值,即参数-XX:CMSInitiatingOccupancyFraction的值,默认值是68,让CMS GC尽早执行,以保证有足够的空间。
  2. 方法二是减少年轻代大小,避免放入年老代时需要分配大的空间,同时调整full gc时压缩碎片的频次,减少持久代大小,以及将触发CMS GC的阀值适当增大

最后总结下,出现Concurrent ModeFailure现象时,解决办法就是要让年老代留有足够的空间,以保证新对象空间的分配。另外在JVM BUG中有提到,JDK1.5_09版本之前,JVM参数-XX:CMSInitiatingOccupancyFraction是无效。

Promotion Failed

为什么会发生Promotion Failed

  1. 一种情况发生在Old区存在大量碎片的情况下,Old区无法容纳下New区转移过来的对象,此时Old区可能并没有满;
  2. 另外一种情况发生在Old区快满了,无法容纳新对象。在所有应用线程停止时进行的GC被称为FGC。

解决办法类似,也是调整年轻代和年老代的比例,还有CMS GC的时机。

小结

对于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。

  1. promotion failed – concurrent mode failure
    1. Minor GC后, 救助空间容纳不了剩余对象,将要放入老年带,老年带有碎片或者不能容纳这些对象,就产生了concurrent mode failure, 然后进行stop-the-world的Serial Old收集器。
    2. 解决办法:-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5 或者 调大新生代或者救助空间
  2. concurrent mode failure
    1. CMS是和业务线程并发运行的,在执行CMS的过程中有业务对象需要在老年带直接分配,例如大对象,但是老年带没有足够的空间来分配,所以导致concurrent mode failure, 然后需要进行stop-the-world的Serial Old收集器。
    2. 解决办法:调小+XX:CMSInitiatingOccupancyFraction,或者减少年轻代大小,来调大老年代的空间

总结一句话:使用标记整理清除碎片和提早进行CMS操作。

参考文档

  1. https://my.oschina.net/hosee/blog/674181
  2. http://www.programgo.com/article/71553203648/
  3. http://blog.csdn.net/cpzhong/article/details/6912272
  4. http://www.voidcn.com/blog/yaooch/article/p-1904640.html
  5. http://www.cnblogs.com/dongxiao-yang/p/5013477.html
  6. http://www.importnew.com/1993.html


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者侯本文授权发布。