JVM内存泄漏本质是本该回收的对象因强引用未断而“该死不死”,核心判断标准为对象无业务用途但GC Roots仍可达;高频泄漏点包括静态集合未清理、监听器未注销、ThreadLocal未remove、内部类隐式持外层引用、资源未关闭;发现手段按场景分层:jstat看老年代阶梯增长、MAT分析dump中Retained Heap大的对象、VisualVM监控类加载数;修复须落地:静态容器换WeakHashMap或Caffeine、监听器配对注销、ThreadLocal必调remove、内部类改静态+弱引用。

画JVM内存泄漏的思维导图,重点不在画得漂亮,而在于抓住“谁漏了、怎么漏、怎么发现、怎么堵”这四条主线。结构清晰、关键词准确、逻辑闭环,一张A4纸就能覆盖核心。
明确泄漏本质:对象该死不死
内存泄漏不是内存用得多,而是本该被回收的对象因强引用未断,持续占用堆空间。关键判断标准:对象已无业务用途,但GC Roots仍能触达它。
- GC Roots典型来源:正在执行的线程栈帧中的局部变量、静态变量、本地方法栈引用、JVM内部对象(如系统类加载器)
- 常见误区:把内存溢出(OOM)直接等同于泄漏——其实OOM可能是配置过小、大对象突增或真正的泄漏,需先排除其他原因
高频泄漏场景与对应节点
导图中每个场景应包含“代码特征 + 内存表现 + 典型位置”,方便快速对标排查。
-
静态集合容器:如
static Map<String, Object> cache = new HashMap<>(),只put不remove,对象长期被静态引用持有着 - 未注销的监听器/回调:GUI事件、Spring事件监听、Netty ChannelHandler添加后未显式移除,导致持有外部对象引用
-
ThreadLocal误用:线程池中线程复用,ThreadLocal变量未手动
remove(),造成Value对象随线程生命周期滞留 - 内部类隐式持外层引用:非静态内部类实例持有外部类this,若内部类被静态变量或长生命周期对象引用,外部类也被拖住
- 资源未关闭 + finalize副作用:虽已弃用finalize,但部分老代码依赖它释放资源,实际不可靠,易引发资源类对象堆积
定位手段要写进导图分支
不能只列工具名,要标注“什么现象下优先用哪个”。
- jmap + jhat 或 Eclipse MAT:发生OOM后生成heap dump,重点看“dominator tree”和“leak suspect report”
- jstat -gc <pid>:观察YGC频率是否稳定、老年代使用率是否单向增长(疑似泄漏)
- VisualVM / JConsole 实时监控:关注“Classes”页签中已加载类数量是否持续上升(可能类加载器泄漏)
- -XX:+HeapDumpOnOutOfMemoryError:必加参数,确保OOM时自动保留现场
修复与预防要点作为收尾节点
导图最后落点必须是可操作动作,避免空泛原则。
- 静态集合优先用WeakHashMap或Guava Cache(支持LRU+自动清理)
- 注册监听器的地方,配套写注销逻辑,并考虑用try-finally或try-with-resources保障执行
- ThreadLocal务必配对使用
set(null)或remove(),尤其在线程复用场景 - 避免非静态内部类暴露给外部作用域;必要时改用静态内部类+弱引用传参
- 单元测试中模拟长周期运行,用Arthas watch命令监控可疑对象实例数变化


















