JVM CMS GC算法解析
发布日期:2021-06-30 15:08:06 浏览次数:3 分类:技术文章

本文共 5232 字,大约阅读时间需要 17 分钟。

CMS,全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性需求 大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。CMS是用于对Old+Perm回收,采用CMS时候,新生代必须使用Serial GC或者ParNew GC两种。目标是尽量减少应用的暂停时间,减少Full   GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。作为ARPG游戏服务器的应用,因为有永久缓存的存在,并且对于玩家操作响应时间也有非常高的要求(12+W/s 数据包),因此希 望能使用CMS来替代默认的server型JVM使用的并行收集器(Parallel MSC),以便获得更短的垃圾回收的暂停时间,提高程序的响应性。
CMS收集周期
CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停,它的收集周期是这样:
初始标记(CMS-initial-mark) -> 并发标记(CMS-concurrent-mark) ->并发预清理(concurrent pre-clean)-> 重新标记(CMS-remark) -> 并发清除(CMS-concurrent-sweep) ->并发重设状态等待下次CMS的触发(CMS-concurrent-reset)。
其中的1,4两个步骤需要暂停所有的应用程序线程的。
第一次暂停从root对象开始标记存活的对象,这个阶段称为初始标记;第二次暂停是在并发标记之后,暂停所有应用程序线程,重新标记并发标记阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致)。第一次暂停会比较短,第二次暂停通常会比较长,并且 remark这个阶段可以并行标记。
而并发标记、并发清除、并发重设阶段的所谓并发,是指一个或者多个垃圾回收线程和应用程序线程并发地运行,垃圾回收线程不会暂停应用程序的执行,如果你有多于一个处理器,那么并发收集线程将与应用线程在不同的处理器上运行,显然,这样的开销就是会降低应用的吞吐量。Remark阶段的并行,是指暂停了所有应用程序后,启动一定数目的垃圾回收进程进行并行标记,此时的应用线程是暂停的。
CMS的young generation的回收采用的仍然是并行复制收集器,这个跟Parallel gc算法是一致的。
一段正常的CMS的日志,CMS的周期一般包含以下步骤:
========初始标记(CMS-initial-mark)============2015-10-10T19:22:19.472+0800: 904337.364: [GC [1 CMS-initial-mark: 1996330K(2124800K)] 2089808K(3270400K), 0.0667700 secs] [Times: user=0.06 sys=0.00, real=0.07 secs] 各个数据依次表示标记前后old区的所有对象占内存大小[1996330K],old的capacity[2124800K],整个JavaHeap(不包括perm)所有对象占内存总的大小[2089808K],JavaHeap的capacity[3270400K]。2015-10-10T19:22:19.538+0800: 904337.430: Total time for which application threads were stopped: 0.0674020 seconds========并发标记(CMS-concurrent-mark)=========2015-10-10T19:22:19.538+0800: 904337.430: [CMS-concurrent-mark-start]2015-10-10T19:22:19.540+0800: 904337.432: Total time for which application threads were stopped: 0.0010340 seconds2015-10-10T19:22:21.471+0800: 904339.363: [CMS-concurrent-mark: 1.931/1.932 secs] [Times: user=6.94 sys=0.30, real=1.93 secs]========并发预清理(concurrent pre-clean)======2015-10-10T19:22:21.471+0800: 904339.363: [CMS-concurrent-preclean-start]2015-10-10T19:22:21.608+0800: 904339.500: [CMS-concurrent-preclean: 0.117/0.137 secs] [Times: user=0.15 sys=0.06, real=0.13 secs] 2015-10-10T19:22:21.608+0800: 904339.500: [CMS-concurrent-abortable-preclean-start]CMS: abort preclean due to time 2015-10-10T19:22:26.798+0800: 904344.690: [CMS-concurrent-abortable-preclean: 5.140/5.190 secs] [Times: user=7.10 sys=1.45, real=5.19 secs] =========重新标记(CMS-remark)=================2015-10-10T19:22:26.799+0800: 904344.691: [GC[YG occupancy: 270973 K (1145600 K)]2015-10-10T19:22:26.799+0800: 904344.691: [Rescan (parallel) , 0.2171280 secs]2015-10-10T19:22:27.016+0800: 904344.908: [weak refs processing, 0.0000700 secs]2015-10-10T19:22:27.016+0800: 904344.908: [scrub string table, 0.0016420 secs] [1 CMS-remark: 1996330K(2124800K)] 2267303K(3270400K), 0.2190720 secs] [Times: user=2.13 sys=0.00, real=0.22 secs] 2015-10-10T19:22:27.021+0800: 904344.913: Total time for which application threads were stopped: 0.2232820 seconds==========并发清除(CMS-concurrent-sweep)======2015-10-10T19:22:27.022+0800: 904344.914: [CMS-concurrent-sweep-start]2015-10-10T19:22:27.970+0800: 904345.862: [CMS-concurrent-sweep: 0.948/0.948 secs] [Times: user=1.53 sys=0.18, real=0.95 secs]==========并发重设状态(CMS-concurrent-reset)==2015-10-10T19:22:27.970+0800: 904345.862: [CMS-concurrent-reset-start]2015-10-10T19:22:27.976+0800: 904345.868: [CMS-concurrent-reset: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
其中可以看到CMS-initial-mark阶段暂停了0.0674020 seconds,而CMS-remark阶段暂停了0.2232820 seconds,因此两次暂停的总共时间是0.290684seconds,也就是290毫秒左右。两次短暂停的时间之和在300ms以下可以称为正常现象。
啥时候会触发CMS GC?
1、旧生代或者持久代已经使用的空间达到设定的百分比时(默认CMS是在tenured generation,也就是old区占满92%的时候开始进行CMS收集,

2、JVM自动触发(JVM的动态策略,也就是悲观策略)(基于之前GC的频率以及旧生代的增长趋势来评估决定什么时候开始执行),如果不希望JVM自行决定,可以通过-XX:UseCMSInitiatingOccupancyOnly=true来制定;

CMS GC因为是在程序运行时进行GC,不会暂停,所以不能等到不够用的时候才去开启GC,官方说法是他们的默认值是68%,但是可惜的是文档写错了,经过很多测试和源码验证这个参数应该是在92%的时候被启动,

-XX:CMSInitiatingOccupancyFraction=70

这样保证Old的内存在使用到70%的时候,就开始启动CMS了;如果你真的想看看默认值,那么就使用参数:-XX:+PrintCMSInitiationStatistics 这个变量只有JDK 1.6可以使用 1.5不可以,查看实际值-XX:+PrintCMSStatistics

reference:

The default for JVM 1.4.2 is 68%, but do NOT assume that the JVM always uses the default if you do not specify this parameter. Instead, analyze the GC logs to understand when the JVM triggers a CMS GC. we determined that a lot of objects were promoted to the tenured generation and CMS GC was mostly triggered at 50% occupancy of the tenured generation with some occurrences between 60% to 75% also

为啥CMS会有内存碎片,如何避免?
ygc变慢的原因通常是由于old gen出现了大量的碎片。
由于在CMS的回收步骤中,没有对内存进行压缩,所以会有内存碎片出现,CMS提供了一个整理碎片的功能,通过-XX:UseCMSCompactAtFullCollection 来启动此功能,启动这个功能后,默认每次执行Full GC的时候会进行整理,目前默认就是true了!(也可以通过-XX:CMSFullGCsBeforeCompaction =n来制定多少次Full GC之后来执行整理),整理碎片会stop-the-world.默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩。  
啥时候会触发Full GC?
一、旧生代空间不足:java.lang.outOfMemoryError:java heap space;
二、Perm空间满:java.lang.outOfMemoryError:PermGen space;
三、CMS GC时出现promotion failed  和concurrent  mode failure(Concurrent mode failure发生的原因一般是CMS正在进行,但是由于old区内存不足,需要尽快回收old区里面的死的java对象,这个时候foreground gc需要被触发,停止所有的java线程,同时终止CMS,直接进行MSC。);
四、统计得到的minor GC晋升到旧生代的平均大小大于旧生代的剩余空间;
五、主动触发Full GC(执行jmap -histo:live [pid])来避免碎片问题;或者System.gc();
什么时候不应该用CMS?

heap小于3g不建议使用CMS GC

参考文章:

转载地址:https://jiangguilong2000.blog.csdn.net/article/details/49030247 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:JAVA各种引用(Reference)的汇总
下一篇:地图开发汇总

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月08日 23时26分44秒