线上服务的GC问题,是Java程序非常典型的一类问题,非常考验工程师排查问题的能力。同时,几乎是面试必考题,但是能真正答好此题的人并不多,要么原理没吃透,要么缺乏实战经验。
过去半年时间里,我们的广告系统出现了多次和GC相关的线上问题,有Full GC过于频繁的,有Young GC耗时过长的,这些问题带来的影响是:GC过程中的程序卡顿,进一步导致服务超时从而影响到广告收入。
这篇文章,我将以一个FGC频繁的线上案例作为引子,详细介绍下GC的排查过程,另外会结合GC的运行原理给出一份实践指南,希望对你有所帮助。内容分成以下3个部分:
-
从一次FGC频繁的线上案例说起
-
GC的运行原理介绍
-
排查FGC问题的实践指南
01 从一次FGC频繁的线上案例说起
去年10月份,我们的广告召回系统在程序上线后收到了FGC频繁的系统告警,通过下面的监控图可以看到:平均每35分钟就进行了一次FGC。而程序上线前,我们的FGC频次大概是2天一次。下面,详细介绍下该问题的排查过程。
1. 检查JVM配置
通过以下命令查看JVM的启动参数:
ps aux | grep "applicationName=adsearch"
-Xms4g -Xmx4g -Xmn2g -Xss1024K
-XX:ParallelGCThreads=5
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSInitiatingOccupancyFraction=80
可以看到堆内存为4G,新生代为2G,老年代也为2G,新生代采用ParNew收集器,老年代采用并发标记清除的CMS收集器,当老年代的内存占用率达到80%时会进行FGC。
进一步通过 jmap -heap 7276 | head -n20 可以得知新生代的Eden区为1.6G,S0和S1区均为0.2G。
2. 观察老年代的内存变化
通过观察老年代的使用情况,可以看到:每次FGC后,内存都能回到500M左右,因此我们排除了内存泄漏的情况。
3. 通过jmap命令查看堆内存中的对象
通过命令 jmap -histo 7276 | head -n20