Full GC后堆内存未明显下降,说明对象因强引用未被回收,需排查内存泄漏或大对象驻留;应分析GC日志中老年代used值、触发原因及类卸载情况,并结合堆转储定位持有引用链。

Full GC 后堆内存未明显下降,通常说明对象没被回收,不是 GC 机制失效,而是存在强引用阻止回收。重点排查内存泄漏或大对象长期驻留。
检查 GC 日志中的关键指标
从日志确认是否真没释放:
- 看前后堆使用量:对比 Full GC 前后 “PSYoungGen”、“ParOldGen” 或 “Metaspace” 的 “used” 值(如 [PSYoungGen: 123456K->1234K(234560K)]),关注 old gen 是否下降不明显
- 看 GC 类型和触发原因:日志中是否有 "Allocation Failure"(正常)还是 "Metadata GC Threshold"(Metaspace 满)、"Ergonomics"(JVM 自适应触发)等异常诱因
- 看是否发生 class unloading:若日志含 "Unloading classes" 且数量极少或为 0,可能类加载器泄漏,导致老年代对象+Class元数据无法释放
定位长期存活的大对象或引用链
GC 不回收 = 对象仍被可达路径引用。需结合堆转储分析:
- 添加 JVM 参数自动导出堆快照:-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=/path/to/dumps/
- 用 jmap -histo:live
快速查看实例数量最多的类,重点关注自定义缓存、静态集合(HashMap、ConcurrentHashMap、ArrayList)、监听器列表等 - 用 Eclipse MAT 打开 hprof 文件,用 Dominator Tree 找最大对象,再用 Path to GC Roots(排除弱/软引用)查看谁在持有着它
排查常见泄漏场景
以下情况极易导致 Full GC 后内存岿然不动:
-
静态集合类缓存未清理:如 private static Map
cache = new HashMap(); ,不断 put 却无淘汰或 clear 逻辑 - ThreadLocal 没 remove:尤其在线程池场景下,线程复用导致 ThreadLocalMap 中的 value 累积(即使 key 为 null,value 仍可达)
- 内部类持有外部类引用:非静态匿名内部类(如事件监听器)被注册到长生命周期对象(如单例 Service),造成外部 Activity/Controller 无法回收
- ClassLoader 泄漏:Web 应用热部署、OSGi、或自定义 ClassLoader 加载类后未释放,其加载的所有类及静态变量均无法卸载
验证与收尾建议
改完代码后不要只看一次 GC 日志,要持续观察:
- 开启详细 GC 日志:-Xlog:gc*,gc+heap=debug,gc+metaspace=debug:file=gc.log:time,tags,level(JDK 10+)或旧版 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
- 配合 jstat -gc
5000 实时监控各代使用趋势,确认 old gen 使用量是否随时间缓慢上涨 - 上线前做压力测试 + 主动触发多次 Full GC(jcmd
VM.runFinalization 或 jmap -histo:live 强制回收),确认内存可回落到基线水平
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











