内存泄漏早期信号是老年代回收后占用逐次升高、释放量递减,如PSOldGen: 6800K→7150K→7620K;新生代出现抢道晋升(age 1占比超80%)、Minor GC激增;OOM类型决定排查方向,需结合MAT分析堆转储定位强引用链。

直接看 GC 日志里“谁涨得快、谁降不下去、谁卡在最后”,就能发现内存泄漏的早期信号。关键不是有没有 GC,而是各代内存变化是否违背正常回收规律。
盯紧老年代的“回落幅度”
重点不是某次 Full GC 后用了多少 MB,而是连续几次 Full GC 后老年代的“回收后占用”是否逐次升高、释放量越来越小:
- 比如日志中反复出现
[PSOldGen: 6800K->7150K(8192K)]→[PSOldGen: 7150K->7620K(8192K)],总大小不变,但每次只挤出几十 KB,说明对象无法被释放; - JDK 8 看
PSOldGen或ParOldGen字段;JDK 11+ 统一日志中搜索Old或tenured标签段; - 若老年代使用率长期 >90%,且 Full GC 后仅从 98% → 95%,回收比低于 20%,基本可判定存在泄漏。
识别新生代的异常“抢道晋升”
Minor GC 频率飙升不一定是新生代问题,常是老年代泄漏的前兆:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- Eden 区秒级填满、Minor GC 每秒多次,但每次只回收一点点 → 可能大量对象因强引用(如静态 Map)滞留,快速达到晋升年龄;
- 开启
-XX:+PrintTenuringDistribution后,若日志中频繁出现age 1: xxx bytes占比超 80%,说明对象几乎不经历 Survivor 就直奔老年代; - 用
jstat -gcutil <pid> 1s 10实时验证:若 YGCT 次数 10 分钟内激增 3 倍以上,而老年代容量(OGCMN/OGCMX)未变,大概率有对象在“抢道晋升”。
区分 OOM 类型,锁定问题方向
日志末尾的报错类型决定排查重心:
立即学习“Java免费学习笔记(深入)”;
-
java.lang.OutOfMemoryError: Java heap space→ 堆空间耗尽,聚焦老年代持续高位、Full GC 回收无效; -
GC overhead limit exceeded→ GC 花了 98% 时间只回收不到 2% 空间,说明回收效率极低,常见于大量短命对象反复创建又难释放; - 无明确 OOM 但 Full GC 越来越频繁、单次耗时 >1s 且老年代使用率几乎不变 → 泄漏初期,需立即抓堆转储验证。
配合堆转储确认“为什么没降”
日志只能告诉你“哪里没降”,要搞清“为什么没降”,必须结合堆快照:
- 启动时加
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump/,OOM 时自动保存; - 或手动执行
jmap -F -dump:format=b,file=heap.hprof <pid>; - 用 Eclipse MAT 打开 dump 文件,按 Dominator Tree 查看占用最大的对象类型,再点开其 Paths to GC Roots 追溯强引用链——常见泄漏源包括静态集合、未注销监听器、线程局部变量、自定义类加载器残留实例等。

















