java内存泄漏表现为堆使用率持续爬升、full gc变慢、重启后快速复现;满足阶梯式上升、老年代长期超90%、响应变慢与fgct飙升重合中任意两点即可判定;需用jstat/jmap抓现场,聚焦静态集合、未注销监听器、threadlocal、资源未关闭、内部类强引用五大源头;mat通过leak suspects、dominator tree和path to gc roots精准定位根因。

Java内存泄漏不是“突然出错”,而是“悄悄吃光内存”——堆使用率持续爬升、Full GC越跑越慢、重启后几天就复现,这些信号比OOM更早、更真实。关键不在等崩溃,而在识别异常趋势、锁定可疑对象、切断无效引用。
看准三类典型泄漏迹象
别等OOM才行动。以下现象满足任意两个,基本可判定存在内存泄漏:
- 堆内存占用呈阶梯式上升,每次Full GC后无法回落到基线(比如从40%→60%→85%,且不下降);
- 老年代(Old Gen)使用率长期高于90%,同时Full GC频率从几小时一次变成几分钟一次;
- 应用响应变慢、接口超时增多,且时间点与GC日志中FGCT飙升高度重合。
注意:业务高峰时内存短暂上涨属正常;真正的问题是“GC清不干净”——回收后残留量越来越高。
用jstat和jmap快速抓取现场证据
无需重启、无需加参数,直接在生产机上执行命令定位问题节奏:
- jstat -gc PID 2000 5:每2秒采样1次,共5次,重点关注OU(Old Used)是否持续增长、FGC(Full GC次数)是否陡增、FGCT(Full GC耗时)是否突破1秒;
- jmap -dump:live,format=b,file=heap.hprof PID:导出存活对象快照(推荐加live参数,避免包含已标记但未回收的对象,更贴近真实泄漏状态);
- 若服务不允许长时间停顿,可用jcmd PID VM.native_memory summary辅助判断是否存在堆外内存异常增长。
聚焦五大高频泄漏源头
80%以上的生产泄漏集中在以下五类代码模式,排查时优先检查:
- 静态集合无清理:static Map/List不断put/add,却从不remove或clear;
- 监听器/回调未注销:注册了EventListener、RxJava订阅、Spring事件监听器,但销毁时没调用remove/unsubscribe;
- ThreadLocal未清理:在线程池场景下,子线程复用导致ThreadLocal变量堆积(尤其存了大对象或Connection);
- 资源未关闭:InputStream/OutputStream、Socket、数据库连接、文件句柄,在try-catch中遗漏finally或未用try-with-resources;
- 内部类强引用外部实例:非静态匿名内部类(如Runnable、Handler)隐式持有Activity/Service/Controller引用,导致整个上下文无法释放。
用MAT精准定位泄漏根因
把heap.hprof导入Eclipse MAT,三步锁定问题对象:
- 打开Leak Suspects Report:工具自动扫描并高亮最可能的泄漏点(通常排第一的嫌疑对象就是突破口);
- 查看Dominator Tree,按“Retained Heap”倒序排列,找占用内存最大、实例数异常多的类;
- 对可疑类右键→Path to GC Roots → exclude weak/soft references:看清是哪个静态字段、哪个ThreadLocal、哪条监听链路把它死死拽住。
例如看到大量User对象被com.example.cache.GlobalCache.INSTANCE持有,那就直接查这个静态缓存类的put逻辑和清理机制。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











