频繁Full GC需先定位诱因:通过GC日志识别类型(Allocation Failure/ Metadata GC Threshold/System.gc()/Humongous Allocation等),用jstat监控O/M/S0S1指标,jmap+MAT分析堆快照找泄漏源,再针对性修复代码或调参。

频繁 Full GC 不是参数乱调就能压下去的,核心在于定位真实诱因并切断对象向老年代的异常堆积路径。盲目增大堆或调高晋升年龄,往往只是延缓爆发,甚至让问题更隐蔽。
看懂 GC 日志,分清 Full GC 类型
先别急着改参数,打开 GC 日志(必须含 -XX:+PrintGCDetails -XX:+PrintGCDateStamps),重点识别日志末尾的触发原因:
- Full GC (Allocation Failure):最常见。说明老年代真没空间了,要么对象晋升太快,要么内存泄漏;
- Full GC (Metadata GC Threshold):元空间快满了,常见于大量动态代理、热部署、反复 defineClass;
-
Full GC (System.gc()):代码或某 SDK 主动调了
System.gc(),尤其是某些老版本 Druid、Log4j 或监控埋点工具; - Full GC (Humongous Allocation):G1 下分配巨型对象失败,比如单次 new byte[8M](Region 默认 1M~4M);
- concurrent mode failure / promotion failed:CMS 或 G1 在并发阶段失败,本质是老年代碎片化或预留空间不足。
用 jstat 快速锁定瓶颈区
运行中执行:jstat -gcutil <pid> 2000,每两秒刷新一次,紧盯三组指标:
- O(Old)持续 ≥90% 且不回落 → 老年代对象只进不出,大概率内存泄漏或晋升失控;
- M(Metaspace)稳步上涨不降 → 类加载泄漏,检查 Spring Context 刷新、Groovy 脚本、MyBatis Mapper 动态生成;
- S0/S1 长期接近 100%,Eden 打满极快 → Survivor 区太小,对象“活过一轮就跳槽”,提前晋升老年代。
抓堆快照,直击内存占用大户
在 O ≥85% 但尚未 OOM 时执行:jmap -dump:live,format=b,file=heap.hprof <pid>(加 live 更准,但会触发一次 Full GC;若不能停业务,可先用不加 live 的版本)。
立即学习“Java免费学习笔记(深入)”;
用 MAT(Eclipse Memory Analyzer)打开后重点关注:
- “Leak Suspects” 报告 —— 自动标出疑似泄漏的集合类(如 static HashMap、ConcurrentHashMap);
- “Dominator Tree” 排序 —— 看哪些类实例最多、总占用最大,特别是
byte[]、char[]、String、ArrayList; - 对可疑对象做 “Path to GC Roots” —— 查清楚谁一直强引用着它,比如线程局部变量、静态监听器、未关闭的流。
针对性调优与代码修复
确认根因后,按类型落地解决:
- 若是 内存泄漏:清理静态缓存、注销监听器、关闭资源、重写 ThreadLocal 的
remove(); - 若是 大对象滥用:用对象池复用 byte[]/StringBuilder/JSON parser;避免单次请求组装超 2MB 的 List 或 JSON;
- 若是 晋升过快:适当加大 Survivor 区(
-XX:SurvivorRatio=6)、降低晋升阈值(-XX:MaxTenuringThreshold=4),让对象多熬几轮 Minor GC; - 若是 元空间泄漏:限制大小(
-XX:MaxMetaspaceSize=256m)、升级框架(避免旧版 Spring Boot 反复刷新 Context)、禁用不必要的字节码增强(如某些 AOP 工具); - 若是 G1 巨型对象:减小 Region 大小(
-XX:G1HeapRegionSize=512K),或改用-XX:+AlwaysPreTouch提前分配,减少运行时碎片。
调优不是终点,而是验证起点。上线后持续观察 FGC 频率、老年代水位、接口 P99 延迟三者是否同步改善。一次真正有效的调优,应该让 Full GC 从“每分钟数次”回归到“数天一次”,且不再随流量增长而线性恶化。


















