我是如何通过调整JVM参数,将CMS GC总时长从8秒缩短至0的

最近公司在对各系统做性能压测,测试过程中发现我们系统的Full GC耗时较长,并且系统在做Full GC的过程中,各相关指标表现的非常不平稳,这迫使我们势必要对系统做一次深入的分析和调整了。

首先附图,CMCGC平均耗时图:


CMCGC平均耗时.png

从图中可以看出,系统在一分钟内每次CMSGC的平均耗时接近8s,这对于实时性要求比较高的系统来说,是不可接受的。

看到这样的数据,我很惊讶,为什么CMSGC耗时会这么长,于是赶紧看了下GC日志:

2020-03-08T01:09:02.782+0800: 6210.125: [GC2020-03-08T01:09:02.783+0800: 6210.126: [ParNew: 7700345K->179389K(8987840K), 0.1289430 secs] 9608343K->2089382K(11084992K), 0.1303150 secs] [Times: user=0.87 sys=0.02, real=0.13 secs] 
2020-03-08T01:09:10.986+0800: 6218.329: [GC2020-03-08T01:09:10.987+0800: 6218.330: [ParNew: 7669309K->231397K(8987840K), 0.1233340 secs] 9579302K->2142805K(11084992K), 0.1249900 secs] [Times: user=0.83 sys=0.02, real=0.12 secs] 
2020-03-08T01:09:19.571+0800: 6226.914: [GC2020-03-08T01:09:19.571+0800: 6226.915: [ParNew: 7721317K->189725K(8987840K), 0.1387220 secs] 9632725K->2102326K(11084992K), 0.1401130 secs] [Times: user=0.85 sys=0.01, real=0.14 secs] 
2020-03-08T01:09:28.452+0800: 6235.795: [GC2020-03-08T01:09:28.453+0800: 6235.796: [ParNew: 7679645K->184868K(8987840K), 0.1044920 secs] 9592246K->2101457K(11084992K), 0.1058950 secs] [Times: user=0.71 sys=0.01, real=0.11 secs] 
2020-03-08T01:09:37.451+0800: 6244.794: [GC2020-03-08T01:09:37.452+0800: 6244.795: [ParNew: 7674788K->180692K(8987840K), 0.1199220 secs] 9591377K->2100461K(11084992K), 0.1212900 secs] [Times: user=0.79 sys=0.01, real=0.12 secs] 
2020-03-08T01:09:46.254+0800: 6253.598: [GC2020-03-08T01:09:46.255+0800: 6253.599: [ParNew: 7670612K->129512K(8987840K), 0.1306460 secs] 9590381K->2075270K(11084992K), 0.1321790 secs] [Times: user=0.86 sys=0.01, real=0.13 secs] 
2020-03-08T01:09:46.390+0800: 6253.734: [GC [1 CMS-initial-mark: 1945758K(2097152K)] 2079340K(11084992K), 0.1071190 secs] [Times: user=0.11 sys=0.00, real=0.11 secs] 
2020-03-08T01:09:46.499+0800: 6253.842: [CMS-concurrent-mark-start]
2020-03-08T01:09:47.188+0800: 6254.531: [CMS-concurrent-mark: 0.689/0.689 secs] [Times: user=4.31 sys=0.52, real=0.69 secs] 
2020-03-08T01:09:47.189+0800: 6254.532: [CMS-concurrent-preclean-start]
2020-03-08T01:09:47.212+0800: 6254.555: [CMS-concurrent-preclean: 0.021/0.023 secs] [Times: user=0.03 sys=0.00, real=0.02 secs] 
2020-03-08T01:09:47.212+0800: 6254.555: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 2020-03-08T01:09:52.995+0800: 6260.338: [CMS-concurrent-abortable-preclean: 5.762/5.782 secs] [Times: user=24.38 sys=2.73, real=5.79 secs] 
2020-03-08T01:09:52.998+0800: 6260.342: [GC[YG occupancy: 5601671 K (8987840 K)]2020-03-08T01:09:52.999+0800: 6260.342: [GC2020-03-08T01:09:52.999+0800: 6260.343: [ParNew: 5601671K->167247K(8987840K), 0.0910250 secs] 7547429K->2116443K(11084992K), 0.0923200 secs] [Times: user=0.61 sys=0.01, real=0.09 secs] 
2020-03-08T01:09:53.092+0800: 6260.435: [Rescan (parallel) , 0.0388170 secs]2020-03-08T01:09:53.131+0800: 6260.474: [weak refs processing, 0.0000360 secs]2020-03-08T01:09:53.131+0800: 6260.474: [scrub string table, 0.0219010 secs] [1 CMS-remark: 1949195K(2097152K)] 2116443K(11084992K), 0.1540610 secs] [Times: user=0.93 sys=0.01, real=0.15 secs] 
2020-03-08T01:09:53.154+0800: 6260.497: [CMS-concurrent-sweep-start]
2020-03-08T01:09:55.067+0800: 6262.410: [CMS-concurrent-sweep: 1.913/1.913 secs] [Times: user=9.52 sys=1.15, real=1.91 secs] 
2020-03-08T01:09:55.068+0800: 6262.411: [CMS-concurrent-reset-start]
2020-03-08T01:09:55.080+0800: 6262.423: [CMS-concurrent-reset: 0.012/0.012 secs] [Times: user=0.08 sys=0.01, real=0.01 secs] 

通过观察GC日志发现,耗时主要发生在[CMS-concurrent-abortable-preclean]阶段,总耗时5.79秒,难怪总耗时会有8秒了

CMS: abort preclean due to time 2020-03-08T01:09:52.995+0800: 6260.338: [CMS-concurrent-abortable-preclean: 5.762/5.782 secs] [Times: user=24.38 sys=2.73, real=5.79 secs] 

我之前写过一篇文章《CMS垃圾回收的7个重要阶段》,里面讲到CMS-concurrent-abortable-preclean 其实跟上一阶段CMS-concurrent-preclean做的事情是一样的,都是为了减少下一个STW重新标记阶段的工作量。为什么用abortable修饰,其实是为了让我们可以控制这个阶段的结束时机,JVM给我门提供了2个参数

  • -XX:CMSMaxAbortablePrecleanTime=5000 ,默认值5s,代表该阶段最大的持续时间
  • -XX:CMSScheduleRemarkEdenPenetration=50 ,默认值50%,代表Eden区使用比例超过50%就结束该阶段进入remark

我们对参数做了以下调整:
1.将该阶段的持续时间缩短成10毫秒
2.将Eden区使用比例设定为100%

-XX:CMSMaxAbortablePrecleanTime=10 
-XX:CMSScheduleRemarkEdenPenetration=100

系统设定的内存参数是"-Xms12g -Xmx12g -Xmn10g",GC参数是"-XX:SurvivorRatio=5",这样算下来,eden区大约占7.14G,s0、s1区各占1.43G,old区占2G。

为何要如此设置呢,我们是这样考虑的:

我们期望CMS-concurrent-abortable-preclean阶段能够清除掉Eden区所有存活对象,直到使得其使用比例达到100%才终止,如果真的如此,Eden区中将没有存活对象或者仅存极少对象,下一次remark阶段就几乎只需要对old区中的对象进行标记,这样可以大大减少remark阶段STW的时间。

另外我们期望CMS-concurrent-abortable-preclean阶段能够尽快结束,期望时间是10毫秒。

把2个参数连贯起来就是,我们希望CMS-concurrent-abortable-preclean阶段在10毫秒之内能够回收Eden区中所有的对象空间,这势必会导致JVM在这个阶段之前做最少一次的minor GC,以保证尽最大可能回收Eden区的空间。

调整的思路是这样了,那到底效果如何呢?
我们将系统重新发布运行,在最近24小时内,系统运行稳定,CMSGC平均耗时为0。

image.png
image.png

不仅如此,我们发现通过调整参数之后,年老代的空间是0,表明没有对象晋升到年老代。后面想了下,确实是这样。系统是实时播发系统,数据播完就结束了,不会常驻在内存中,所有的数据都是临时数据。按照我们设置的参数,每次minor gc的时候,就会把所有临时的数据清掉。在清理过程中产生的浮动对象,会被放在survivor区,下一次清理的时候,该survivor区中的对象又被清理掉了,导致永远没有数据进入到年老代,也就没有了CMSGC。

你可能感兴趣的:(我是如何通过调整JVM参数,将CMS GC总时长从8秒缩短至0的)