线上Java内存泄漏排查需基于证据链:先通过错误类型、GC行为和时间规律确认真泄漏;再用jmap/jstat/jstack抓取堆快照、GC日志和线程快照;接着用MAT分析Leak Suspects及GC Roots定位静态集合、ThreadLocal等泄漏源;最后精准修复而非仅调大堆内存。

线上 Java 内存问题不是“会不会发生”,而是“什么时候发生”。一次没及时处理的内存泄漏,可能让服务在流量高峰时突然雪崩;一个未清理的 ThreadLocal 或静态集合,可能悄悄吃掉几 GB 堆空间,直到 OOM 报警响起才被发现。真正有效的排查,不靠猜,而靠证据链:从现象定位 → 工具取证 → 快照分析 → 代码溯源 → 验证闭环。
看现象:先分清是“真泄漏”还是“假溢出”
别一看到 OOM 就急着 dump。先快速确认三件事:
-
错误类型:日志里是
Java heap space(堆满)、Metaspace(类加载爆炸)、Direct buffer memory(NIO 堆外泄漏),还是unable to create new native thread(线程数超限)?不同错误对应完全不同的排查路径。 -
GC 行为:用
jstat -gcutil <pid> 2000每两秒刷一次。如果老年代使用率持续 95%+、Full GC 频繁但回收极少,大概率是对象无法释放;如果年轻代频繁 Minor GC 且回收正常,但整体堆压不下来,说明大对象或长生命周期引用在“钉住”内存。 - 时间规律:是否随定时任务触发(如每分钟缓存刷新)、随特定接口调用(如导出 Excel)、或随部署后小时级缓慢上涨?这直接指向代码上下文。
取证据:保留现场比重启更重要
服务卡死或告警时,第一反应不是 kill -9,而是立即抓取三类关键数据:
-
堆快照:执行
jmap -dump:live,format=b,file=heap.hprof <pid>。注意加live参数只导出存活对象,避免干扰;若进程已僵死,可提前配置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump/实现自动捕获。 -
GC 日志:启动时启用
-Xlog:gc*:file=gc.log:time(JDK 11+)或-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log(旧版),从中能看清每次 GC 回收了多少、哪些区域没动、是否出现 promotion failure。 -
线程快照:运行
jstack <pid> > thread.log,重点看是否有大量 WAITING 线程堆积(如锁竞争)、或 RUNNABLE 线程在执行可疑逻辑(如无限循环、大集合遍历)。
查根源:用 MAT 看懂谁在“占着茅坑”
把 heap.hprof 丢进 Eclipse MAT,核心操作就两步:
立即学习“Java免费学习笔记(深入)”;
-
打开 Leak Suspects Report:它会自动标出最可疑的几个对象及它们占用的内存比例。比如显示 “
java.util.ArrayList占用 87% 堆”,那就立刻点进去看它的Retained Heap和References。 -
追踪 GC Roots:右键该 ArrayList → “Path to GC Roots” → 选 “with all references”。你会看到一条引用链,例如:
CacheService.cache → ArrayList → DataDTO。再顺藤摸瓜找到CacheService类里那个没清空的静态 List,或没 remove 的 ThreadLocal。
常见泄漏模式包括:静态集合无限 add、监听器注册后未注销、ThreadLocal 使用后未调用 remove()、缓存未设上限或过期策略、NIO 的 ByteBuffer.allocateDirect() 未显式 clean。
修代码:修复要精准,别只调大堆
修复不是简单加 -Xmx8g,而是切断泄漏源头:
-
静态集合:改用带过期机制的缓存,如 Guava Cache 或 Caffeine,避免手写
static Map;若必须用,加上定时清理或容量限制。 -
ThreadLocal:在业务逻辑结束前(如 Filter 的
doFilter末尾、Spring AOP afterReturning),显式调用threadLocal.remove()。 -
流与资源:所有
InputStream、Connection、ByteBuffer必须用 try-with-resources 包裹,尤其 GZIPInputStream、ZipInputStream 这类装饰流容易漏关。 - 第三方组件:检查是否用了未关闭的 OkHttp ConnectionPool、未 shutdown 的 ScheduledExecutorService、或未 deregister 的 JMX MBean。
修复后,务必用压力测试验证内存曲线是否平稳下降,而不是靠“好像没挂”来判断。


















