内存泄漏早期最典型特征:老年代/堆内存呈缓慢、持续、不可逆的上涨,但还没严重到频繁Full GC、OOM。
这个阶段最容易定位根因,核心思路是:监控趋势 → 定位泄漏区域 → 抓取堆快照 → 分析泄漏对象 → 定位代码。
下面给你一套生产环境可直接落地的排查流程,从简单到深入,不影响业务。
一、第一步:先确认「真的是内存泄漏」
先排除正常内存使用、堆设置过小等情况。
1. 看内存增长曲线(最关键)
通过监控看堆内存/老年代趋势:
- 正常:GC 后内存会大幅回落,曲线呈锯齿状
- 泄漏:每次GC后回落很少,整体持续缓慢上涨
2. 快速命令验证(无需工具)
jstat-gcutil<pid>100010# 每1秒输出一次GC情况,共10次重点看 3 个指标:
O:老年代使用率FGC:Full GC 次数FGCT:Full GC 总耗时
泄漏早期特征:
O持续缓慢上升(比如从 30% → 40% → 50%)FGC很少(0~几次)- 每次Full GC后,老年代下降幅度极小
只要满足「老年代只涨不跌」,基本就是内存泄漏。
二、第二步:定位泄漏类型(区分堆内/堆外)
绝大多数泄漏是堆内泄漏,极少数是堆外。
1. 堆内泄漏(95%场景)
- 特征:老年代持续上涨
- 原因:对象被长生命周期引用持有(静态集合、线程池、缓存、单例)
2. 堆外泄漏
- 特征:堆内存正常,但进程物理内存持续涨
- 原因:NIO DirectByteBuffer、JNI、池化资源未释放
先按堆内泄漏排查,效率最高。
三、第三步:无侵入抓取堆快照(核心步骤)
不需要重启、不需要压测、低影响,生产直接用。
推荐抓取时机
- 一次Full GC之后立即抓(最干净,能精准定位泄漏对象)
- 或者内存涨到相对高位时抓
抓取命令
jmap-dump:format=b,file=leak.hprof<pid>- 生成
leak.hprof二进制堆转储文件 - 大小≈当前堆使用量,注意磁盘空间
小提示:JDK8+ 加
-dump:live可以只抓存活对象,文件更小:jmap -dump:live,format=b,file=leak.hprof<pid>
四、第四步:用工具分析泄漏(10分钟定位代码)
最常用、最稳定的工具:Eclipse MAT(免费、强大)
1. MAT 打开 hprof 文件
选择Acquire Heap Dump / Open Heap Dump
2. 直接使用「泄漏嫌疑报告」
MAT 会自动生成:Leak Suspects Report
它会直接告诉你:
- 哪个类占用内存最大
- 被谁引用
- 哪行代码导致泄漏
3. 核心分析 3 步
- Histogram(直方图)
看对象实例数,找数量异常多的业务对象(你的自定义类) - Merge Shortest Paths to GC Roots
右键对象 → 查看 GC Root 引用链 - 排除弱引用/软引用
只看强引用链,泄漏一定在这里
泄漏对象的典型特征
- 你的业务实体类(User/Order/Config等)实例数只增不减
- 被static Map/List/Set持有
- 被线程池 / 单例 / 缓存持有
- 被ThreadLocal持有(高频泄漏点)
五、第五步:早期泄漏最常见的 5 种代码根因
你在分析时,优先排查这 5 类代码,90% 泄漏都在这里:
1. 静态集合(最常见)
privatestaticList<Object>list=newArrayList<>();// 只add不remove,对象永远无法回收2. ThreadLocal 未清理
privatestaticThreadLocal<Object>tl=newThreadLocal<>();// 线程池中的线程长期存活,ThreadLocalMap 一直持有对象3. 线程池/自定义线程忘记关闭
- 线程是 GC Root,线程里的引用全部泄漏
- 线程池任务里持有大对象,且任务无限等待
4. 缓存未设置过期/淘汰
privatestaticMap<String,Object>CACHE=newHashMap<>();// 无过期、无淘汰,无限增长5. IO/连接未关闭
- InputStream、Socket、Connection 未 close
- 底层持有堆外/堆内引用
六、无工具、命令行快速初筛(应急用)
如果不能用MAT,可以先用命令粗定位:
jmap-histo<pid>|head-20输出:实例数、字节数、类名
找自定义类、实例数异常高的类,就是怀疑对象。
七、最佳实践:提前发现早期泄漏
不用等问题爆发,配置监控即可:
- 监控老年代使用率趋势
- 监控GC后内存回落幅度
- 配置告警:老年代连续 10 次 GC 不下降
总结(极简排查口诀)
- 看趋势:老年代只涨不跌 = 泄漏
- 抓快照:Full GC 后 jmap 抓 hprof
- 用MAT:看 Leak Suspects
- 查引用:静态集合、ThreadLocal、线程池、缓存
- 看代码:谁长期持有对象不释放