频繁full gc问题应分三步排查:先用jstat验证是否真频繁,再区分gc类型(如老年代持续增长指向内存泄漏),最后用jmap导出堆快照并用mat分析dominant tree和leak suspects定位内存大户及泄漏源。

直接看 GC 行为本身,而不是等用户投诉或监控告警才行动。高频 GC 的典型表现是响应变慢、CPU 持续偏高、偶发卡顿,但这些只是症状。真正高效的做法,是从 JVM 运行时状态出发,分三步锁定根因:确认是否真频繁、区分 GC 类型、再定位内存问题源头。
第一步:用 jstat 快速验证是否真频繁 GC
别依赖日志或猜测,执行这条命令实时观察:
jstat -gc每秒输出一次,共 10 行。重点关注四列:
- YGC:Young GC 次数 —— 每秒 ≥2 次,说明新生代压力大
- YGCT:Young GC 总耗时 —— 占比超 10%,已影响业务吞吐
- FGC:Full GC 次数 —— 每分钟 ≥1 次,属于紧急级别,必须立刻处理
- FGCT:Full GC 总耗时 —— 单次 >500ms,用户明显感知卡顿
如果 FGC 在涨,哪怕只涨 1 次/分钟,也要进入深度排查;YGC 高但 FGC 不涨,大概率是新生代太小或对象创建过快,调参优先级高于代码修复。
第二步:区分是哪一类 GC 问题
不同类型的 GC 异常,排查路径完全不同:
- 频繁 Full GC:老年代使用率持续上涨、回收后仍居高不下 → 典型内存泄漏或堆配置过小
- 高频 Young GC:YGC 次数猛增,但老年代几乎不动 → 新生代(-Xmn)可能只有几百 MB,或业务在短周期内疯狂 new 对象
- Young GC 后老年代缓慢增长,最终触发 Full GC:最常见于内存泄漏 → 对象本该被回收,却因静态引用、ThreadLocal 未清理、缓存未过期等原因滞留老年代
用 jmap -heap
第三步:导出并分析堆快照找“内存大户”
确认是内存类问题后,导出快照是最关键一步:
jmap -dump:live,format=b,file=heap.hprof加 live 参数只抓存活对象,减少 STW 时间,适合低峰期执行。然后用 Eclipse MAT 打开分析,重点看三个视图:
- Dominator Tree:找出直接占用最多内存的实例,比如某个 HashMap 占了 1.2G
- Leak Suspects Report:MAT 自动生成的疑似泄漏报告,通常准度很高
- Objects by Class:按类统计实例数,若某业务 POJO 实例达百万级且持续增长,就是泄漏铁证
常见泄漏点包括:static Map/List 缓存无清理机制、ThreadLocal 存储对象后没 remove、流/连接未 close 导致关联对象无法释放。
补充:别忽略 GC 线程和容器环境的影响
在 Docker/K8s 环境中,JVM 默认按物理 CPU 核数算 GC 线程数,容易超额。比如容器只分配了 2 核,但宿主机有 32 核,JVM 可能起 23 个 ParallelGCThreads,反而造成线程争抢。建议显式设置:
-XX:ParallelGCThreads=2 -XX:ConcGCThreads=1再配合 -XX:+UseContainerSupport(JDK8u191+),让 JVM 正确读取容器限制。这步虽不解决泄漏,但能避免 GC 本身成为性能干扰项。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











