JVM频繁Full GC导致服务不可用定位过程
JVM频繁Full GC导致服务不可用定位过程
背景:
公司推行微服务策略,我负责的XX模块相对于其他业务来讲相对独立,所以作为微服务推行的试点。于是
- 分析业务边界 ;
-
做相关的架构升级:
- 从Spring3.X升级到Spring5.X(引入了SpringBoot2.0)
- 从JDK7升级到JDK8(老年代被MetaSpace取代)
- ShardingJDBC从1.4版本升级到3.00.M1版本
问题描述:
重构系统上线后,发现运行一段时间后,系统会出现频繁的Full GC,频率几乎1秒一次,导致服务长时间卡死(10+mins到30+mins不等),但是卡死一段时间后会恢复,再运行一段时间,服务会被Linux内核kill了。
JVM启动参数配置:
java -Dspring.profiles.active=prod -server -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseParallelGC -XX:+UseParallelOldGC -verbose:gc -Xloggc:/mnt/gc.log -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/mnt/fc-income.dump -XX:-OmitStackTraceInFastThrow -jar /mnt/app/income.jar &
Jstat 实时监控
从上图可以看出Eden和Old都满了,导致几乎2秒一次的full gc,此时,服务大部分时间耗在full gc,而full gc是stop the world。
真相逐渐浮现
既然是堆内存不够,那么就加大堆内存
java -Dspring.profiles.active=prod -server -Xms8g -Xmx8g -Xmn3g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseParallelGC -XX:+UseParallelOldGC -verbose:gc -Xloggc:/mnt/gc.log -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/mnt/fc-income.dump -XX:-OmitStackTraceInFastThrow -jar /mnt/app/income.jar &
即使加大了堆内存,服务还是运行一段时间后频繁full gc
这时候发现,年轻代和老年代的使用率都很低,连MetaSpace的使用率也只有67.34%,但是几乎是2秒就一次full gc。
此时就百思不得其解了,为什么明明堆内存没满,可是就是full gc呢??
jstat 分析gc原因:
[root]# jstat -options
-class -- 显示加载class的数量,及所占空间等信息
-compiler -- 显示VM实时编译的数量等信息
-gc -- 可以显示gc的信息,查看gc的次数,及时间
-gccapacity -- VM内存中三代(young,old,perm)对象的使用和占用大小
-gccause -- 统计gc信息,并且打印gc的原因
-gcmetacapacity -- metaSpace的信息及其占用量
-gcnew -- 年轻代对象的信息
-gcnewcapacity -- 年轻代对象的信息及其占用量
-gcold -- old代对象的信息
-gcoldcapacity -- old代对象的信息及其占用量
-gcutil -- 统计gc信息(显示使用的百分比)
-printcompilation-- 当前VM执行的信息
这时候,使用jstat -gccause pid 来分析引起gc的原因

这时候就很明显了,是由于metaSpace空间满了导致的。一般来讲,metaSpace存放的是类的元数据。
使用mat辅助分析
1258869328/1024/1024/1024 ~=1.2G
请参考文章
groovy导致的内存泄漏
定位哪里引用了groovy导致内存泄漏
利用:mvn dependency:tree查看项目依赖关系
很明显,就是之前重构的时候升级ShardingJDBC引入的坑。
查看ShardingJDBC发版记录
刚好在3.0.0M2里面解决了这个问题(原来使用的是M1版本)
写在最后
生产环境千万别弄不稳定的版本,里程碑版本其实是不稳定的。。