内存泄漏是对象不再被使用却因错误引用无法被gc回收,本质是生命周期管理失控;典型场景包括静态集合、未注销监听器、threadlocal未清理、内部类隐式引用及资源未关闭。

第一步:快速确认是回收不及时,还是真泄漏
别一看到内存涨就调大堆内存或重启。先看三个硬指标:
- 执行 Full GC 后老年代内存是否明显回落?如果只降5%~10%,甚至不降,大概率是泄漏;若能回落70%以上,更可能是短时压力大(比如批量导入),属内存膨胀
- 观察 GC overhead limit exceeded 日志是否高频出现?这是JVM在警告:“我花98%时间GC,才回收2%内存”,典型泄漏信号
- 用 jstat -gc
1000 看连续10秒的GC行为:Minor GC频次正常但Old Gen使用率单向爬升,且Full GC间隔越来越短 → 泄漏已进入老年代
第二步:抓现场,锁定“赖着不走”的对象
趁内存还没爆,立刻导出堆快照:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 手动触发(推荐):jmap -dump:live,format=b,file=heap.hprof
(加 live 参数确保只抓活跃对象,避免干扰) - 或配置自动兜底:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump/
- 用 Eclipse MAT 打开 hprof 文件,直奔三个视图:
- Histogram → 按 "Retained Heap" 排序,找前3名:是不是大量 byte[]、String、ArrayList、HashMap.Entry?这些往往是缓存、日志、临时数据的载体
- Dominator Tree → 看谁“支配”了最多内存,点开它,看直接引用它的上层对象是谁
- Leak Suspects Report → MAT 自动生成的报告,通常第一嫌疑就是泄漏根因(比如某个 static Map 占了60%内存)
第三步:查代码,切断不该存在的引用链
常见“拽着不放”的5类源头,对应修复动作:
-
静态集合无限堆积:如
private static Map<string object> cache = new HashMap();</string>→ 改用WeakHashMap,或引入Caffeine并设maximumSize(1000)和expireAfterWrite(10, TimeUnit.MINUTES) - 监听器/回调未注销:注册了事件监听、Spring ApplicationListener、Netty ChannelHandler 后,务必在销毁逻辑里显式 remove 或 close
-
ThreadLocal 未清理:尤其在线程池场景下,线程复用导致 ThreadLocal 变量跨请求残留 → 在 finally 块或拦截器中调用
threadLocal.remove() -
内部类持有外部类:非静态内部类会隐式持外部类 this 引用 → 若只需访问静态方法/常量,改为
static class;否则用WeakReference<outer></outer>持有 - 资源流未关闭:InputStream、Connection、ResultSet 等 → 统一改用 try-with-resources,杜绝仅靠 finalize 回收
第四步:验证修复是否生效
改完不能只测功能,要测内存行为:
- 本地压测:用 JMeter 模拟相同流量,监控 VisualVM 中堆内存曲线 —— 应呈现“锯齿状”波动(涨后必降),而非阶梯式上升
- 线上灰度:小流量发布,重点盯 Grafana 的
jvm_memory_used_bytes{area="heap"}和jvm_gc_pause_seconds_count{action="endOfMajorGC"}指标 - 对比基线:同一业务周期(如1小时)内,Full GC 次数下降、单次耗时缩短、老年代峰值降低30%以上,才算真正解决
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










