Java应用频繁Full GC主因是内存泄漏推高老年代水位,应通过jstat观察O列持续上涨、FGC增多、FGCT升高锁定问题,用jmap -dump:live抓取堆快照,再用MAT的Dominator Tree和Histogram结合业务逻辑定位静态集合、ThreadLocal等典型泄漏点。

Java 应用出现频繁 Full GC,多数不是 GC 策略问题,而是内存泄漏在“悄悄推高老年代水位”。真正要做的,不是调参数,而是揪出那些本该被回收、却因强引用滞留的老对象。
看老年代是否“只增不减”
这是最直接、最可靠的泄漏信号。别只盯着 FGC 次数多,重点看每次 Full GC 后老年代使用率(O 或 OU)回落多少:
- 健康状态:GC 后老年代占用率下降 50% 以上,曲线呈明显锯齿状
- 泄漏特征:GC 后仅下降 2%~5%,甚至几乎不降,且长期单向爬升(比如从 40% → 65% → 88%)
- 辅助验证:YGC/FGC 比值接近 1:1 或倒挂,说明大量对象没活过年轻代就晋升了,也可能是泄漏或大对象误入
用 jstat -gcutil <pid> 1s 持续观察 10~20 秒,重点关注 O(老年代使用率)、FGC、FGCT 三列。若 O 持续上涨 + FGC 增多 + FGCT 累积升高,基本可锁定泄漏。
轻量抓取堆快照,避开生产风险
别一上来就 jmap -dump 硬导出——大堆下容易 STW 过久,影响线上服务。优先采用低侵入方式:
立即学习“Java免费学习笔记(深入)”;
- 提前配置:加 JVM 参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dumps/,等 OOM 自动触发,最稳妥 - 手动抓取时,加
live参数:jmap -dump:live,format=b,file=heap.hprof <pid>,只导存活对象,文件小、分析快、停顿短 - 避免
jmap -histo:live,它会强制触发一次 Full GC,可能雪上加霜
用 MAT 精准定位泄漏源头
MAT 不是数谁对象多,而是查“谁在死死拽着不该活的对象”。打开 hprof 后,聚焦两个视图:
- Dominator Tree:按 Retained Heap 排序,找前三名大对象;右键 → “Path to GC Roots” → 选 “with all references”,重点看业务类(如 UserService、CacheManager、Scheduler)是否意外持有了海量 byte[]、String 或集合
- Histogram:筛选实例数暴增的类(比如 ArrayList、HashMap$Node、自定义实体);右键该类 → “List objects” → “with incoming references”;对可疑实例逐个查 “Path to GC Roots”,确认引用链是否合理(例如:静态 Map → 缓存条目 → 大数组)
别依赖 “Leak Suspects Report”,它误报率高。真正关键的是你结合业务逻辑判断引用是否必要。
验证并修复典型泄漏点
90% 的泄漏集中在几类模式,找到后快速验证:
-
静态集合无限膨胀:检查
static Map、static List是否只存不删;用 Arthas 执行ognl '@com.xxx.Cache@map.size()'实时查大小 -
ThreadLocal 未清理:尤其在线程池场景,线程复用导致 value 累积;检查是否在 finally 块中调用
remove() - 监听器/回调未注销:GUI 或事件驱动框架中注册后未解绑,造成持有链不断延长
-
DirectByteBuffer 堆外泄漏:若堆内存正常但进程 RSS 持续涨,用
jcmd <pid> VM.native_memory summary查堆外使用
修复后上线,继续用 jstat 观察 OU 趋势——若 GC 后回落明显、不再单边上涨,说明问题已解决。


















