写作目的
最近上线了一个需求,遇到了一个JVM报警的问题,很荣幸能遇到,在此分享一下整个调优的过程。
背景
我们是中台服务,我们的甲方就是上游不同的业务。中台原则上是业务和能力分离,但是不可避免的是分不开,所以我们通过SPI的机制让上游的业务实现SPI接口从而执行他们自己的逻辑。本次需求我们上线了一个大需求,要同时发布很多业务方实现的SPI包。我们是灰度发布,发布一台机器后发现频繁的FGC导致监控报警。
补充一下我们的机器规格是4核4G内存和80G磁盘。GC垃圾收集器是CMS和ParNew。
接下来开始进行推理和论证。
推断堆空间有问题
频繁的FGC初步想法就是OOM,比如静态集合无限添加对象。但是去机器上找了一下OOM的Dump文件这个是没找到的,所以说只能手动dump。
jmap -dump:format=b,file=/tmp/myapp_dump.bin pid1234
使用MAT去分析,去找自己的类最多的,发现并没有找到那种一枝独秀特别多的。下面是反例,公司不方便截图。
因为是灰度,所以我们有正常的机器。对比了有问题机器和没问题机器新生代和老年带的变化趋势、速成和使用大小,整体是相似的,这块就不符合常理了。类似下面的图。
结论:新生代老年代和正常机器一样,初步推断堆空间正常。
推断元空间有问题
如果不是堆空间引起的FGC,那就是元空间要满了。接着通过arthas的dashboard命令对比,这里发现这个值新老机器差距很大。
那就需要调大元空间
把
-XX:MetaspaceSize=1500m
-XX:MaxMetaspaceSize=1500mm
修改为
-XX:MetaspaceSize=2048m
-XX:MaxMetaspaceSize=2048m
此时机器再发布后就不会出现FGC了。
结论:元空间小,导致频繁FGC
元空间到底为什么变大
通过JVM的命令,可以看出来加载了哪些类
jcmd 28818 GC.class_histogram
那我看上图有什么意义呢?
对比新老机器,可以获得两份加载的类
通过awk命令能洗出来有哪些类,然后通过diff命令就可以看出来不同的类。
通过分析两个加载的类不同,发现两个问题。
- 本次加载的类有本次需求新上的,符合预期。
- 里面有很多MethodAccessor,看我的另一篇文章
总结
- 如果面试回答,你可以说你引入了很多pom,这种场景其实是很常见的。
- 本文对回答的关键词都做了加深,包括每一步的命令是什么,且能能量化的都用数字表示(比如机器大小)
- 调大元空间意味着别的会缩小,所以最终方法是充钱,扩大机器的配置。
课外补充
补充一下有意义的jvm启动参数
-XX:ParallelGCThreads=4 (并行收集,几核机器设置几核)
-Xms6g (调优,设置新生代初始大小)
-Xmx6g (调优,设置新生代最大值)
-Xmn2g (调优,设置堆空间大小)
-XX:MetaspaceSize=2048m
-XX:MaxMetaspaceSize=2048m
-XX:MaxDirectMemorySize=1g
-XX:SurvivorRatio=8 (新老年代默认8:1:1)
-XX:+UseConcMarkSweepGC (使用CMS垃圾收集器)
-XX:CMSMaxAbortablePrecleanTime=5000 (并发标记阶段之后、重新标记阶段之前,就让你执行这么长时间)
-XX:+CMSClassUnloadingEnabled (允许类卸载,比如线上使用内存诊断工具Arthas,用完后会有残留)
-XX:CMSInitiatingOccupancyFraction=80 (老年带到达80%,触发老年代收集)
-XX:+UseCMSInitiatingOccupancyOnly(配合上面参数使用)
-XX:+ExplicitGCInvokesConcurrent (针对System.gc()触发老年带的GC,否则就是fullGC)
-Xloggc:/home/admin/logs/gc.log (GC日志目录)
-XX:+PrintGCDetails (GC日志详细细节)
-XX:+PrintGCDateStamps(每个垃圾收集事件发生的确切日期和时间戳)
-XX:+HeapDumpOnOutOfMemoryError (OOM)
-XX:HeapDumpPath=/home/admin/logs/java.hprof (OOM)
参考
有过JVM调优经验吗【面试题】
阿里淘天Java开发工程师,CSDN博客专家,阿里云博客专家,专注于后端技术的分享。如果你迷茫,不妨来瞅瞅码农的轨迹。