三步定位Java内存泄漏:查字节码确认非静态内部类是否生成this$0字段,分析持有方生命周期(如线程池、静态容器、Handler等),再通过Heap Dump追踪GC Roots路径验证引用链;日常代码审查应重点检查非静态内部类Runnable、延时消息未移除、静态集合未清理及ThreadLocal未remove等高危写法。

直接查字节码、看持有方生命周期、追踪 GC Roots 路径,三步就能定位。不是等 OOM 才动手,日常代码审查和运行期快照就能提前发现。
查字节码确认是否生成 this$0 引用
非静态内部类编译后一定会多一个 this$0 字段,这是泄漏的根源证据。执行以下命令:
- javap -c OuterClass$InnerClass,观察输出中是否存在 final OuterClass this$0
- 如果有,说明该内部类强持外部实例;静态内部类或 Lambda(仅捕获局部 final 变量)则不会出现
- Android Studio 在编辑器里对 Handler 等典型场景会直接标黄提示 “This Handler class should be static or leaks might occur”
分析谁在长期持有这个内部类
真正导致泄漏的,不是内部类本身,而是把它“钉住”的那个长生命周期对象。重点关注:
- 线程池:Runnable 被 submit 后未 cancel,Worker 持有其引用,进而锁住整个外部类
-
静态容器:如 public static List
HOLDERS ,注册后没反注册 - Handler / Looper:尤其主线程 Handler 发送延时消息,Message.target 持有 Handler,Handler 隐式持 Activity/Fragment
- EventBus / RxJava Disposable / TimerTask:订阅未解绑、Disposable 未 dispose、TimerTask 未 cancel
用 Heap Dump 追踪 GC Roots 路径
一旦怀疑,立刻导出堆快照,用 VisualVM、JProfiler 或 Eclipse MAT 分析:
- 搜索疑似泄漏的外部类实例(如某个 Activity 或 Service)
- 右键 → Merge Shortest Paths to GC Roots
- 若路径为:OuterClass$InnerClass → this$0 → OuterClass,且中间穿插 ThreadPoolExecutor$Worker 或 static field,即为实锤
代码审查时重点扫描这几类写法
不依赖工具也能提前拦截,Review 时盯紧:
- 所有 new Thread(Runnable)、executor.submit() 中的 Runnable 是否为非静态内部类或匿名类
- 所有 view.postDelayed()、handler.postDelayed() 的 Runnable 是否在 onDestroy() / onCleared() 中调用 removeCallbacks()
- 所有 static Map/Collection 是否配套清理逻辑(如定时 purge、LRU 驱逐、注册时同步存注销句柄)
- 所有 ThreadLocal> 是否在 finally 块中调用 tl.remove()(不只是 set(null))
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











