内存泄漏的本质是该回收的对象因强引用未被回收,需通过jstat观察OU持续上涨、Full GC后不回落等GC行为异常来识别,而非仅看内存占用高或OOM。

先看现象,再定性质:内存占用高不等于泄漏,也不一定就是溢出。关键在判断对象是否该回收却没被回收——这才是泄漏;而溢出是“连申请都失败了”的硬性崩溃。
盯住 GC 行为,三秒识别泄漏苗头
别等 OOM 报错才行动,用 jstat -gcutil <pid> 2000 每两秒刷新一次,重点看:
- OU(老年代使用率)持续单向上涨,Full GC 后只回落一点点甚至不回落 → 强烈提示泄漏
- EU(Eden 区使用率)每次 Young GC 后下降幅度越来越小 → 说明大量对象“赖在 Eden 不走”,可能被意外持有
- S0U/S1U 长期接近 100% 且无明显波动 → 幸存区对象无法晋升或清理,常与弱引用误用、缓存未淘汰有关
查存活对象分布,快速筛异常类
执行 jmap -histo:live <pid>(注意必须加 :live),重点关注:
- 某个业务类(如
com.example.OrderDetail)实例数达几十万以上,远超并发量 → 很可能是静态集合或监听器未注销 -
byte[]、char[]占比异常高 → 暗示 Base64 解码残留、流未关闭、日志缓冲区堆积 - 大量
java.util.HashMap$Node或java.util.ArrayList→ 集合类无节制膨胀,检查是否用了static Map缓存但忘了清理
区分泄漏和溢出的四个硬指标
满足其中两项,基本可锁定为泄漏;若仅出现第4条,则更倾向溢出:
立即学习“Java免费学习笔记(深入)”;
- 堆内存随时间缓慢但稳定上升,重启后短时间内复现相同增长曲线
- GC 日志频繁出现
GC overhead limit exceeded - 老年代长期 >90%,Full GC 耗时超过 1 秒且频率越来越高
- 直接抛出
java.lang.OutOfMemoryError: Java heap space,且堆 dump 显示大量同一类对象占满空间,但引用链清晰合理(比如真要存百万订单)→ 更可能是设计容量不足,而非泄漏
补一手:OOM 时自动抓现场
上线前务必加上这两个 JVM 参数:
-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/opt/dumps/
这样一旦真爆 OOM,会自动生成 .hprof 文件,后续用 MAT 打开就能看 Dominator Tree 和 Leak Suspects,不用临时手忙脚乱做 dump。


















