当前位置: 首页 > 科技观察

jvm系列(六):Java服务GC参数调优案例

时间:2023-03-13 14:41:07 科技观察

本文介绍了生产环境下JVMGC相关参数的调优过程。通过参数调整,避免GCfreeze对JAVA服务成功率的影响。这段时间在整理jvm系列文章,无意中发现了这篇文章。作者思路清晰,通过一步一步的分析最终解决了问题。我个人很喜欢这种实用的内容。征得原作者授权同意,将文章分享于此。备注部分是我自己加的,主要是为了说明。原文出处:https://segmentfault.com/a/1190000005174819背景及遇到的问题我们的JavaHTTP服务属于OLTP类型,对成功率和响应时间要求比较高,生产环境偶尔会出现成功率突然下降然后自动恢复的情况,如图:JVM和GC相关的参数如下:-Xmx22528m-Xms22528m-XX:NewRatio=2-XX:+UseConcMarkSweepGC-XX:+UseParNewGC-XX:+CMSParallelRemarkEnabled,由于服务中大量使用Cache,堆大小增加到22G。GC算法使用CMS(UseConcMarkSweepGC),启用减少标记暂停(CMSParallelRemarkEnabled),设置新生代为并行收集(UseParNewGC),新生代与老年代的比例为1:2(NewRatio=2).JVMGC日志相关的参数如下:-Xloggc:/data/gc.log-XX:GCLogFileSize=10M-XX:NumberOfGCLogFiles=10-XX:+UseGCLogFileRotation-XX:+PrintGCDateStamps-XX:+PrintGCTimeStamps-XX:+PrintGCDetails-XX:+DisableExplicitGC-verbose:gc问题解决过程消除了应用的内存占用问题。首先使用jmap查看内存使用情况:jmap-histo:livePID该命令将程序中当前对象按照个数和占用空间排序并打印出来。此处未找到使用异常的对象。消除Cache内容过多的问题。如果Cache内容过多,JVM的老年代很容易被用完,导致GC频繁。于是调出GC日志查看,发现每次GC后内存占用普遍从20G减少到5G。因此,常驻内存Cache并不是造成GC长时间freeze的根本原因。查看GCLOG的方式有很多种。使用VisualVM更直观,需要使用VisualGC:从图中我们可以看到Eden和老年代的空间分配。由于整体内存为20G,设置-XX:NewRatio=2所以老年代为14G,Eden+S0+S1=7G调整GC时间点(成功率抖动问题加剧)。如果GC需要处理的内存量比较大,那么执行时间会比较长,STW(StoptheWorld)时间也会比较长。按照这个思路,调整CMS启动的时间点,希望能让GC更早一些,也就是让GC更频繁,但期望每次执行的时间更少。添加了以下两个参数:-XX:+UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction=50表示当Old区域使用到50%时触发GC。实验后发现GC的频率增加了,但是每次GC造成的Chenpowerreduction现象并没有减弱,所以舍弃这两个参数。调整对象在年轻代内存中的停留时间(效果不明显)。如果能降低老年代GC的频率,也能降低GC的影响。因此,尽量让对象在年轻代的内存中驻留更长时间。增加这些对象在年轻代GC中被销毁的概率。用参数-XX:MaxTenuringThreshold=31调整后,效果不明显。备注:1.MaxTenuringThreshold在1.5.005之前可以设置最大值为31,1.5.006之后可以设置最大值为15。如果超过15个,则视为最大值。2.增加年轻代GC被销毁的概率,但是调整这个参数效果不大,第二次会重新计算age的值。在CMS-Remark之前,年轻代的GC被迫先补充CMS的相关知识。CMS的整个过程中有两步是STW,如红色部分所示:CMS并没有没有停顿,而是用两次短停顿来代替序列标记排序算法的长停顿,其采集周期为如下:1.初始标记(CMS-initial-mark),从根对象开始标记存活对象2.并发标记(CMS-concurrent-mark)3.重启标记(CMS-remark),挂起所有应用线程,并重新标记并发标记阶段遗漏的对象(并发标记阶段结束后对象状态更新引起)4.并发清除(CMS-concurrent-sweep)5.并发重置状态等待下一次CMS触发(CMS-并发重置)。通过对比GC日志和成功率下降的时间点,发现并不是老年代的每次GC都会导致成功率下降,但是发现了一个规律:前两次GCCMS-Remark过程造成4s左右成功率下降,但是第三次??GC对成功率没有太大影响,CMS-Remark只有0.18s。JavaHTTP服务是通过Nginx反向代理的,nginx设置的超时时间是3s,所以GC如果在3s以内卡死,对成功率影响不大。从GC日志中又查到一条信息:蓝色部分的意思,在文档和相关资料中没有查到。猜测是remark处理的内存量,处理的越多越慢。在remark阶段和FULLGC阶段之前添加如下两个参数,强制进行一次新生代GC,使得需要处理的内存量为XX:+ScavengeBeforeFullGC-XX:+CMSScavengeBeforeRemark备注:1.蓝色部分:备注标记需要清理对象的容量。2.在FULLGC阶段之前进行新生代GC的意思是Yong区的对象引用Old区的对象。如果在清理Old区之前没有清理Yong区,则Old区会被Yong区引用。无法释放该对象。调好后,效果很明显。下面是两台完全相同配置的服务器在同一时间段内的成功率和响应时间监控图。不添加强制年轻代GC的第一个参数。结论1.在CMS-remark阶段,需要处理堆中的所有内存对象。如果强制在这个阶段之前执行一次新生代GC,那么remark需要处理的内存量会大大减少,从而降低JVMfreeze对成功率的影响。影响。2、对于JavaHTTP服务,JVM冻结时间要小于HTTP客户端调用超时时间,否则JVM冻结会影响成功率。【本文为专栏作家《纯洁的微笑》原创稿件,转载请微信♂联系作者获得授权】点此查看该作者更多好文