记一次java程序假死的处理过程(JVM参数调优)

前景提要
休假的时候生产服务器出现了一次假死,现象是外部无法访问,批处理任务也不再运行,由于当时不在现场,客户直接kill了进程,导致没法对现场日志或者现场情况进行采集。
赶回公司后它们重启已解决问题,但是领导对这次事件反应非常强烈,要求必须查明原因,一头雾水的我就开始了这填坑之路。
填坑历程
由于缺失现场一手资料,只能靠他们描述的现象对事故现象进行模拟,虽然一波三折,但是最终还是在50线程每秒5次并发的情况下复现了该场景。
压测的堆内存图如下:
记一次java程序假死的处理过程(JVM参数调优)_第1张图片
老年代内存图如下:
记一次java程序假死的处理过程(JVM参数调优)_第2张图片
1、压测过程中我发现老年代内存线性上涨,一开始以为是内存泄露导致内存溢出,但是查看日志发现直到系统假死并没有相关错误输出,于是突然灵光一现,注意到系统在假死前有过一次fullgc,gc后内存占用明显降低,这个时候基本上可以排除内存泄露的原因了(内存泄露是因为回收不了);
2、那么就要着手其他因素,这个时候我注意到压力测试仅仅进行了十分种,但是ygc次数有一万多次,明显有过于频繁的回收发生,回去查看jvm的参数配置,整个堆大小有2g,但是年轻代仅仅分配了256m,我们处理的业务流程比较长,平均时间在1s左右,那么这个时候就会导致一秒内多次gc不掉的对象会被放入幸存区,再次查看幸存区大小,发现仅有可怜的20m,这就导致在高并发情况下会进行高频率的年轻代回收,在请求的生命周期还未结束前就已经达到放入老年代的条件,这个时候就会导致老年代内存直线上升。
3、如上分析已基本找到问题所在,但是没有明确为什么FullGC会导致应用假死,经过一番推论和查证,最终在GC日志中找到了下面的信息;

原来是因为上线时没有针对业务特点进行JVM参数的调整,配置的JVM新生代空间太小引起YGC触发频率过高,从而大量短生命周期的对象过早进入老年代,老年代内存占用随时间直线上升,进而引起触发FullGc,特别的,在未指定FullGC触发节点的情况下,JVM预留内存空间不足以满足客户请求的内存需求时,在某次YGC时因为servivor空间不足先触发了promot failure,接着想放到Old区时发现就触发了concurrent mode failure,此时JVM启动备案操作,临时启动SerialOld收集器来重新进行老年代垃圾回收,引起Stop World Time,应用假死;
解决方案
1、修改-Xmn为-Xmx一半大小(根据业务及项目情况调整);
2、添加XX:SurvivorRatio=8(根据内存变化调整:若老年代依旧持续增长,则可调低该参数)
3、修改Xloggc:路径为可用路径;(由于之前没检查该路径,一直没有生成该文件,给排查问题带来极大不便)
4、添加-XX:+UseCMSInitiatingOccupancyOnly 指定触发条件只有用户指定
5、添加-XX:CMSInitiatingOccupancyFraction=80 指定老年代内存占用达到80%触发CMS
6、添加-XX:+CMSScavengeBeforeRemark (FullGC前先进行YGC)
7、一个可选参数(-XX:+CMSParallelRemarkEnabled)开启并行重标记
修改后
同样的测试条件下,堆内存图如下,老年代几乎无增长,证明参数修改有效。同时,由于青年代垃圾回收占用CPU资源的释放,压力测试tps上升20%左右
记一次java程序假死的处理过程(JVM参数调优)_第3张图片

你可能感兴趣的:(java,JVM调优)