之前的JVM专题总共大致近50章节,问题介绍相关的JVM的原理分析,比较接近于理论化,并且也穿插这相关的GC问题排查和调优的技术功能实现,但是很多小伙伴都建议我多针对于实际优化角度和故障排查而言,进行相关的分析和总结,所以有了目前的【线上解决方案系列】希望大家会喜欢!
堆内内存泄漏总是和GC异常相伴。不过GC问题不只是和内存问题相关,还有可能引起CPU负载、网络问题等系列并发症,只是相对来说和内存联系紧密些,所以我们在此单独总结一下GC相关问题。
使用jstat来获取当前GC分代变化信息。而更多时候,我们是通过GC日志来排查问题的,在启动参数中加上
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
来开启GC日志。
针对GC日志,我们就能大致推断出youngGC与fullGC是否过于频繁或者耗时过长,从而对症下药。我们下面将对G1垃圾收集器来做分析,这边也建议大家使用G1-XX:+UseG1GC
。
youngGC
频繁一般是短周期小对象较多
先考虑是不是Eden区/新生代设置的太小了。
看能否通过调整-Xmn、-XX:SurvivorRatio
等参数设置来解决问题。
如果参数正常,但是
young gc
频率还是太高,就需要使用Jmap和MAT对dump文件进行进一步排查了。
耗时过长问题就要看GC日志里耗时耗在哪一块了。
以G1日志为例,可以关注Root Scanning、Object Copy、Ref Proc等阶段。
Ref Proc耗时长,就要注意引用相关的对象。
Root Scanning耗时长,就要注意线程数、跨代引用。
Object Copy则需要关注对象生存周期。
而且耗时分析它需要横向比较,就是和其他项目或者正常时间段的耗时比较。比如说图中的Root Scanning和正常时间段比增长较多,那就是起的线程太多了。
G1中更多的还是mixedGC,但mixedGC可以和youngGC思路一样去排查。触发fullGC了一般都会有问题,G1会退化使用Serial收集器来完成垃圾的清理工作,暂停时长达到秒级别,可以说是半跪了。
fullGC的原因可能包括以下这些,以及参数调整方面的一些思路:
并发阶段失败:在并发标记阶段,MixGC之前老年代就被填满了,那么这时候G1就会放弃标记周期。这种情况,可能就需要增加堆大小,或者调整并发标记线程数 -XX:ConcGCThreads。
晋升失败:在GC的时候没有足够的内存供存活/晋升对象使用,所以触发了FullGC。
这时候可以通过-XX:G1ReservePercent
来增加预留内存百分比,减少-XX:InitiatingHeapOccupancyPercent
来提前启动标记,-XX:ConcGCThreads来增加标记线程数也是可以的。
大对象分配失败:大对象找不到合适的region空间进行分配,就会进行fullGC,这种情况下可以增大内存或者增大-XX:G1HeapRegionSize
。
程序主动执行System.gc:不要随便写就对了
另外,我们可以在启动参数中配置-XX:HeapDumpPath=/xxx/dump.hprof
来dump fullGC相关的文件,并通过jinfo来进行gc前后的dump
jinfo -flag +HeapDumpBeforeFullGC pid
jinfo -flag +HeapDumpAfterFullGC pid
这样得到2份dump文件,对比后主要关注被gc掉的问题对象来定位问题。