java线上服务卡顿若源于gc,需通过gc日志分析三类核心信息:gc类型与频率、各代内存变化趋势、单次停顿时长;生产环境必须启用-xx:+printgcdetails等参数,并用jstat、jmap等工具交叉验证,快速定位full gc、内存泄漏或配置不当问题。

Java线上服务卡顿,如果根源在GC,GC日志就是最直接的证据。关键不是“有没有日志”,而是看懂三类核心信息:GC类型与频率、各代内存变化趋势、单次停顿时长。只要日志开着,问题通常藏在数字背后,而不是代码里。
第一步:确认日志是否启用且完整
没日志,一切归零。生产环境必须配置以下基础参数(JDK 8+):
- -XX:+PrintGCDetails:输出每次GC的详细内存变化
- -XX:+PrintGCDateStamps:带日期时间戳,方便关联监控告警
- -Xloggc:/path/to/gc.log:指定日志路径,避免写到系统盘根目录
- -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M:启用滚动,防磁盘打满
如果当前没开,先用 jstat -gcutil <pid> 1000 10</pid> 实时抓10秒快照,至少保底看到老年代(O列)使用率是否长期>95%。
第二步:快速识别卡顿是否由Full GC引发
打开日志,直接搜索 [Full GC。重点看三处:
- 频率:几分钟一次?每小时几次?正常系统Full GC应是几小时甚至几天一次;若每分钟都出现,基本可断定是卡顿主因
-
耗时:末尾的
0.xxxxxxx secs—— 超过1秒就会影响接口P99;超过3秒,用户必然感知明显卡顿 -
回收效果:如
[ParOldGen:20480K->20475K(20480K)],说明老年代只回收了5K,几乎没释放空间,意味着对象持续堆积或泄漏
再对比同时间段的 [GC (Allocation Failure)(Young GC),如果Young GC很频繁但老年代仍不断上涨,大概率是对象晋升过快或大对象直接进老年代。
第三步:结合jstat交叉验证关键指标
仅看日志可能漏掉动态趋势。立刻执行:
-
jstat -gcutil <pid> 2000</pid>(每2秒刷新一次)——盯住 O(Old)列:是否缓慢爬升至98%+后突降(Full GC触发),之后又快速回升?这是典型的老年代内存吃紧信号 -
jstat -gccapacity <pid></pid>—— 查看各代实际容量,确认是不是老年代本身配得太小(比如-XX:MaxOldSize远低于业务所需) -
jmap -heap <pid></pid>—— 核对JVM实际生效的堆结构,排除启动参数未生效或被覆盖的情况
若O列长期>90%,且FGC次数陡增,基本可以停止排查数据库或网络,聚焦堆内对象行为。
第四步:定位元凶——从日志线索反推对象行为
日志里的细节就是破案线索:
- Full GC原因写 Ergonomics:说明JVM自适应策略判定需扩容或调整,常因-XX:+UseAdaptiveSizePolicy开启,但更深层可能是年轻代太小、对象晋升太快
-
Full GC原因写 System.gc():检查代码或依赖库是否显式调用了
System.gc(),尤其注意NIO DirectByteBuffer、某些国产中间件SDK - ParOldGen回收前后几乎不变(如20480K→20475K):高度提示内存泄漏,此时必须立刻获取堆dump分析存活对象
- Metaspace使用量持续上涨:关注类加载器泄漏,特别是OSGi、热部署框架、频繁动态生成类的场景
此时配合 jmap -histo <pid> | head -20</pid> 看前20名对象实例数和大小,常能一眼发现异常缓存、未关闭的监听器或静态集合。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











