java内存泄漏本质是对象该回收却因活跃引用链未被回收,需通过gc日志确认老年代持续增长、full gc回收无效,再用jmap抓堆转储并借助mat分析dominator tree、leak suspects及gc roots路径,重点排查静态变量、threadlocal、单例缓存和未注销监听器四类高危源。

Java 应用内存泄漏不是“内存丢了”,而是对象该回收却没被回收——因为还被某个活跃引用链牵着。真正的问题往往藏在代码习惯里,而不是 JVM 本身。
看 GC 日志,先确认是不是真泄漏
别一看到内存涨就断定是泄漏。打开 GC 日志(-XX:+PrintGCDetails -XX:+PrintGCDateStamps),重点观察:
- 老年代(Old Gen)使用率是否持续缓慢上升,且每次 Full GC 后回收量极少
- 年轻代 GC 频率正常,但老年代 GC 越来越频繁、耗时越来越长
- 堆外内存(如 DirectByteBuffer)或元空间(Metaspace)也在同步增长,说明可能不是纯堆泄漏
如果老年代在多次 Full GC 后仍无法释放大量空间,基本可锁定为堆内对象泄漏。
抓堆转储(Heap Dump),找“占坑不走”的对象
用 jmap -dump:live,format=b,file=heap.hprof
- Dominator Tree:看谁占内存最多,按“Retained Heap”排序,找到可疑大对象(比如一个几 MB 的 byte[] 数组、超大的 HashMap)
- Leak Suspects Report:MAT 自动生成的初步诊断,常能直接指出泄漏路径
- Path to GC Roots:右键可疑对象 → “Show Objects with Incoming References” → “Merge Shortest Paths to GC Roots”,看清是谁在强持有它
特别注意:静态变量、ThreadLocal、单例缓存、未注销的监听器,这四类是 80% 以上泄漏的源头。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
查代码,盯住三类高危结构
不用通读全项目,聚焦以下典型模式:
-
静态集合类:如
private static List<object> cache = new ArrayList();</object>—— 没清理机制,对象永远不释放 -
ThreadLocal 未 remove():线程池复用时,上一个请求塞进去的对象会一直留在当前线程里;务必在 finally 块或 filter 中调用
tl.remove() - 资源未关闭 + 强引用监听器:数据库连接、文件流、NIO Channel 没用 try-with-resources;Swing/Android/Spring EventListener 注册后没对应 unregister
内部类隐式持外部类引用也容易漏掉——若内部类生命周期比外部类长(如作为回调传给异步服务),考虑改用 static 内部类 + WeakReference。
验证与收尾:别跳过“重启后是否重现”
修复后别急着上线。做两件事:
- 压测验证:模拟相同流量跑 1–2 小时,用 jstat -gc
5s 观察老年代是否平稳,不再爬升 - 对比快照:重启应用后取一次 baseline heap dump,再跑一段时间再取一次,用 MAT 的 Compare Basket 功能比对对象数量变化
真正的泄漏修复,不是让 OOM 不报了,而是让老年代内存曲线回归平缓、可预测。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










