堆内存使用率高但gc无法回收,说明对象被强引用持续持有,需通过jstat确认现象、jmap保dump、mat分析leak suspects和dominator tree定位强引用源,再针对性修复threadlocal未remove、静态缓存无限制等根因。

堆内存使用率高但 GC 无法回收,说明对象正被强引用持续持有,GC 线程在“白忙活”——不是回收机制失效,而是对象根本不可达回收条件。核心动作不是调参数或扩堆,而是定位谁在“死死拽着内存”,并切断这条引用链。
先确认是不是 GC 型内存问题
别急着 dump 或调参,先看现象是否匹配:
- 用 jstat -gc
观察:老年代使用率长期 >95%,Full GC 后下降不足 3% - 监控中 Full GC 频次突增(例如每分钟 ≥10 次),单次耗时长、回收量却只有几 MB
- 日志出现 java.lang.OutOfMemoryError: Java heap space 或 GC overhead limit exceeded
- 服务响应明显变慢、线程卡顿、GC 线程 CPU 占用飙升
立即保现场,拿到有效 dump
OOM 发生后第一反应不是重启,而是保留证据:
- 若已配置 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump,直接取最新 .hprof 文件
- 若未开启且进程尚可响应,立刻执行:jmap -dump:format=b,file=heap.hprof
(注意:会触发 STW) - 同步抓一段 GC 日志:jstat -gc -h10
2000 (每 2 秒输出,持续 20 秒)
用 MAT 找“不该活着的对象”
打开 dump 后,重点不在“最大对象”,而在“本该被回收却一直存活”的对象:
- 优先看 Leak Suspects Report —— MAT 自动生成的泄漏线索,常直指 ThreadLocal、静态缓存、类加载器等根因
- 再看 Dominator Tree,按 retained heap 排序,找真正支配大量内存的实例(如某个 static Map、未清理的 SessionContextCache)
- 对可疑对象右键 → Path to GC Roots → exclude weak/soft/phantom references,看清是谁在强引用它
- 重点关注:static 字段、ThreadLocal 变量、单例内部缓存、注册未注销的监听器、阻塞队列无背压策略
修复要断源头,不止清一次缓存
很多修复只做 list.clear(),但没解决“为什么还在往里加”。必须从设计层切断增长链:
- ThreadLocal 使用后显式调用 remove(),尤其在线程复用场景(如 Tomcat 线程池)
- 缓存改用 WeakHashMap 或带过期/容量限制的 Caffeine,避免长期持引用
- 异步任务队列加上背压策略(如 LinkedBlockingQueue 改为 ArrayBlockingQueue + 拒绝策略)
- GroovyClassLoader 等动态类加载器,确保使用完及时 close(),防止元空间和老年代双重泄漏
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











