首页 > 编程语言 >JVM CMS GC算法解析

JVM CMS GC算法解析

时间:2022-12-27 19:04:15浏览次数:41  
标签:10 22 0800 concurrent GC JVM CMS


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 seconds
2015-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



参考文章:

一次CMS GC问题排查过程(理解原理+读懂GC日志)

标签:10,22,0800,concurrent,GC,JVM,CMS
From: https://blog.51cto.com/u_4176761/5973345

相关文章