频繁full gc本质是老年代承压报警,须按“确认现象→分析gc日志→抓取堆快照→验证根因”四步定位内存泄漏或对象堆积,严禁盲目调参。

生产环境出现频繁 Full GC,本质是 JVM 在持续报警:老年代快撑不住了。这不是调参能糊弄过去的性能抖动,而是内存泄漏或对象堆积的明确信号。排查必须直奔根因,跳过猜测,用数据说话。
第一步:确认是不是真频繁
别一上来就 dump 堆、改参数。先用最轻量方式验证现象:
-
jstat -gc
1000 10 :每秒输出一次 GC 统计,连看 10 秒。紧盯 FGC 列——如果每分钟涨 ≥1 次,就是紧急级别 -
jstat -gcutil
1000 :观察 O(老年代使用率) 是否持续爬升,比如从 60% → 85% → 95%,且每次 Full GC 后只降 1%~2%,基本锁定泄漏 - 查 GC 日志里有没有 “Full GC (System)” ——说明代码或第三方库在显式调用
System.gc(),这是人为触发,优先排查
第二步:看 GC 日志定类型
GC 日志是唯一能还原“为什么发生”的原始证据。确保已开启:-Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=10,filesize=10M
- 日志中反复出现 “Allocation Failure” + 老年代几乎不下降 → 对象无法回收,指向内存泄漏
- 出现 “Metaspace allocation failure” → 元空间满,重点查动态代理、Groovy 脚本、热部署、类加载器未释放
- 连续出现 “promotion failed” 或 “to-space overflow” → 新生代对象晋升失败,常因 Survivor 区太小或对象存活时间过长
第三步:抓堆快照定位“谁占了老年代”
时机很重要:在老年代使用率 80%+、还没 OOM 时 dump,避免现场丢失。
-
jmap -dump:live,format=b,file=heap.hprof
:加 live参数只导出可达对象,减少 STW,更准 - 快速初筛:jmap -histo:live
| head -20 ,重点关注[B(byte[])、HashMap$Node、ArrayList、自定义大对象类名 - 深度分析:用 Eclipse MAT 打开 hprof 文件,直接看 Dominator Tree(找 Retained Heap 最大的对象),再点开它的 Path to GC Roots —— 那条引用链的起点,就是泄漏源头
第四步:盯住高频泄漏场景
不用大海捞针,先检查这几类代码:
-
静态集合无清理:比如
private static Map<string object> cache = new HashMap();</string>,没设上限、没过期、没手动清理 - ThreadLocal 未 remove:尤其在线程池场景下,线程复用导致 value 累积不释放
- 监听器/回调注册后没反注册:GUI、事件总线、Spring ApplicationListener 等常见
- 缓存未设淘汰策略:用 ConcurrentHashMap 或 Guava Cache 但没配 size limit 或 expireAfterWrite
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











