如何高效解决 Java 内存泄漏问题方法论

如何高效解决 Java 内存泄漏问题 – 目前最实用、最成体系的方法论
(强烈建议收藏)

先把最核心的排查顺序和思维路径记住(非常重要!)

最推荐的内存泄漏排查 大致优先级顺序(由快→慢,由大概率→小概率)

1. 看 GC 日志中哪个区域一直涨得最凶(最重要!)
   ↓
2. 看是不是 堆内存里 **老年代持续上涨** 且 **几乎没有被回收**
   ↓
3. 看是不是 **堆外内存**(Direct ByteBuffer / Netty / Unsafe)在疯狂增长
   ↓
4. 确定是**老年代**问题 → 优先找「**大量的长生命周期对象**」
   ↓
5. 找「**被长生命周期对象强引用的集合**」 → 最最最常见的元凶
   ↓
6. 再找「**各种缓存没有过期策略/淘汰策略**」
   ↓
7. 再找「**线程、连接、资源没有正确关闭**」
   ↓
8. 再找「**各种 Listener、Observer、回调没注销**」
   ↓
9. 最后才考虑「**非常非常隐蔽的泄漏**」(ThreadLocal、classloader、JNI等)

目前业界最推荐的 8步排查法(强烈建议按这个顺序来)

步骤主要目的主要看什么 / 常用命令命中率排序
1先判断是不是真的泄漏GC日志、GC频率、老年代增长曲线、Full GC频率★★★★★
2确定是哪个代区在泄漏Old区 / Survivor / Eden / Metaspace / CodeCache / Direct★★★★★
3最快找到最可疑的大对象jmap -histo:live / VisualVM / jcmd GC.class_histogram★★★★☆
4找引用链最强的那个dump → MAT / jhat / heap dump 分析工具★★★★☆
5看保留集最大的那一类对象MAT → Dominator Tree / Component Report★★★★
6重点看各种集合类HashMap / ConcurrentHashMap / ArrayList / LinkedList / WeakHashMap / Guava Cache / Caffeine★★★★
7重点看各种“持有型”对象ThreadLocal / Thread / Connection / Session / Listener / Observer / ScheduledExecutorService / Timer★★★
8看有没有堆外内存泄漏NMT(Native Memory Tracking)、jcmd、pmap、VisualVM 插件★★☆

最高频、最经典的 13 种 Java 内存泄漏场景(按出现频率排序)

排名场景典型特征发现方式修复难度
1各种 Map / List / Set 长期越积越多最最最常见dump → 看保留集最大的类★★☆
2没有设置过期时间的各种缓存Guava/Caffeine/Ehcache/HashMap做缓存看缓存对象数量持续上涨★☆☆
3ThreadLocal 用完没有 remove()线程池 + ThreadLocal 是地狱组合dump → 看 ThreadLocalMap★★
4各种 Listener / Observer 没注销事件总线、GUI、Spring 事件、MQ消费者等看 Listener 集合持续增长★★☆
5连接池/会话/资源没有 close()HttpClient、数据库连接、Socket、Stream等看连接对象持续增长★★
6单例持有大量临时对象静态 Map / List / 各种 Factory 持有看静态字段引用的对象特别多★★☆
7线程没结束(死循环/阻塞)自定义线程、线程池任务阻塞看线程数量持续增加★★★
8大对象一直被强引用全局配置、大的静态 byte[]/char[]看 Dominator Tree 前几名★★
9Netty ByteBuf 没有 releaseNetty 程序最常见的泄漏点看 DirectByteBuffer 疯狂增长★★★
10类加载器泄漏(热部署/插件系统)spring-boot-devtools / osgi / 自定义类加载器看 Metaspace 持续上涨★★★★
11Unsafe / DirectByteBuffer 滥用自己手动分配了又不释放用 NMT 查看 external/native★★★★
12WeakHashMap 做缓存却把 key 强引用非常经典的错误用法看 WeakHashMap 没被回收★★★
13Lambda/匿名内部类 引用外部对象比较隐蔽,但会把外部对象拉长生命周期看 $ 匿名类引用链★★★☆

快速定位口诀(背下来非常有用)

老年代一直涨 → 看大对象 → 看集合 → 看缓存 → 看 ThreadLocal
线程池 + ThreadLocal → 十有八九是 ThreadLocal 没 remove
Netty + 堆外疯涨 → 99% 是 ByteBuf 没 release
Metaspace 一直涨 → 怀疑热部署/类加载器/大量动态代理/CGLIB
GC 日志里 Full GC 非常频繁且回收很少 → 基本可以断定是泄漏

目前最高效的整套工具链组合(2024-2025主流)

顺序工具主要用途速度推荐指数
1GC日志 + GC easy快速判断是不是泄漏、哪个代区★★★★★★★★★★
2jcmd / jmap -histo快速看活着的对象分布★★★★★★★★☆
3jstack + arthas看线程、看锁、看阻塞★★★★★★★★
4Heap Dump + Eclipse MAT最强引用链分析★★★★★★★★
5VisualVM + 插件综合监控、堆外、类、线程★★★★★★★★☆
6jcmd GC.run/finalizer强制执行 finalizer 看效果★★★★★
7NMT + jcmd VM.native_memory堆外内存泄漏专用★★★★★★(堆外必备)

最后总结 —— 最高效的排查思维导图(建议保存)

发现内存泄漏
    ├── 看 GC日志 → 老年代持续上涨? → 是 → 进入堆内排查
    │                      │
    │                      └─ 不是 → 看是不是堆外 / Metaspace / CodeCache
    │
    └── 堆内泄漏
          ├── 优先 dump → MAT
          │     ├── 看 Dominator Tree 前10
          │     ├── 看 Leak Suspects
          │     ├── 看 Component Report(集合类)
          │     └── 看 ThreadLocalMap 那一项
          │
          ├── 快速看一遍 live histogram
          └── 重点排查下面这几个(顺序)
                1. 所有 static Map/List/Set/缓存
                2. ThreadLocal
                3. 所有 Listener/Callback/Observer
                4. 所有连接池/客户端/资源对象
                5. 自己写的各种长生命周期大容器

口诀先GC日志 → 再大对象 → 再集合 → 再 ThreadLocal → 最后堆外

有想针对其中某一步某种具体场景某种工具的使用细节深入的,可以继续告诉我~

文章已创建 4455

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部