直接查看mat中dominator tree按retained heap降序排列的顶部对象,结合双快照对比、gc roots引用链追踪及jstat/jmap实时指标验证,可准确定位内存泄漏根因。

直接看堆快照里的 Dominator Tree,按 Retained Heap 降序排,排第一的就是实际撑大内存的对象——它被谁引用、占了多少、有没有在持续变大,都能顺藤摸瓜查清楚。
用 MAT 打开快照,直奔 Dominator Tree
堆快照(.hprof 文件)导入 Eclipse MAT 后,打开 Dominator Tree 视图。这个视图反映的是“谁真正控制着多少内存”:一个对象的 Retained Heap = 它自己 + 所有只能通过它访问到的对象总大小。排序后顶部几行就是内存占用真正的“大户”。常见高占比对象包括:
- 某个静态 Map(如
CacheManager.INSTANCE.cacheMap) - 未清理的监听器集合(如
ArrayList持有成千上万个回调) - 线程池中堆积的
FutureTask或未完成的Runnable - 大缓存容器里塞满的
byte[]或String实例
确认是不是真在“涨”,不只看单次快照
单次快照只能告诉你“此刻谁最大”,但持续增长得靠对比。至少抓两个时间点的快照:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 基线快照:服务刚启动、负载平稳时(比如 jmap -dump:live,file=heap_01.hprof
) - 问题快照:内存明显上涨后、OOM 前几分钟再抓一次(heap_02.hprof)
在 MAT 中用 Compare Basket 功能加载两个快照,点击 Compare,默认按 Shallow Heap 排序,重点看 Delta 列为正、且 Retained Heap 增长剧烈的类。比如某 OrderCache 类 Retained Heap 从 50MB 涨到 1.2GB,就基本锁定了目标。
顺着引用链,找到创建它和持有它的代码位置
选中 Dominator Tree 里那个暴涨的对象,右键 → Path to GC Roots → with all references(务必勾选“exclude weak/soft/phantom”)。路径终点通常暴露根因:
- 停在
java.lang.Thread→ 查该线程栈,定位是哪个定时任务或异步方法不断 new 对象 - 停在
static字段 → 看字段名和类名,比如com.example.service.LogAggregator.ALL_LOGS,直接对应代码行 - 停在
ThreadLocalMap→ 检查是否用了 ThreadLocal 却没调 remove() - 停在
RequestContextHolder→ Spring 请求上下文泄漏,多见于异步线程里误用了 RequestScope Bean
辅助验证:别光信快照,交叉看运行时指标
快照结论要和实时指标对得上才可靠:
- 用
jstat -gc -t <pid> 5s</pid>持续采样,观察 OU(老年代已使用)是否阶梯式上升,且每次 Full GC 后回落极少 - 用
jmap -histo <pid></pid>快速比对对象数量变化,比如byte[]实例数从 2 万涨到 15 万,说明 IO 或序列化环节可能出问题 - 如果怀疑是元空间涨,加
jstat -metaspace <pid></pid>,持续上涨往往指向动态代理类没卸载(如 MyBatis Mapper、Spring AOP)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










