非静态内部类会隐式持有外部类的强引用,因编译器自动生成final字段this$0并绑定外部实例,导致外部类无法被gc回收;匿名、局部及普通非静态内部类均适用此规则。

非静态内部类会隐式持有外部类的强引用,这是编译器自动插入的 this$0 字段所致。只要这个内部类实例被长期持有,外部类就无法被 GC 回收——哪怕业务早已结束。
为什么非静态内部类一定带强引用
Java 编译器在生成字节码时,会为每个非静态内部类添加一个 final 类型的合成字段 this$0,其类型就是外部类。构造该内部类时,编译器自动将外部类实例传入并赋值,形成不可切断的强引用链。
- 匿名内部类、局部内部类、普通非静态内部类,全部适用此规则
- 静态内部类不生成
this$0,因此不会引发此类泄漏 - Lambda 表达式若捕获实例变量或
this,行为等效于匿名内部类
哪些场景最容易出问题
泄漏是否发生,关键看内部类是否被“长期持有”。以下模式风险极高:
- 向
ScheduledExecutorService或线程池提交非静态Runnable/Callable - 在 Activity/Fragment(Android)或 Servlet(Web)中定义非静态
Handler、TimerTask或回调实现类,并注册给系统组件 - 将内部类实例赋值给静态集合、单例成员或全局缓存
- 作为监听器添加到长生命周期对象(如 EventBus 订阅、RxJava
Disposable),但未及时解注册
清理不是简单设 null,而是断引用链
在 onDestroy 或 finally 中把内部类变量设为 null 没用——只要线程池、消息队列、监听容器还持有它,this$0 就依然有效。
- 对线程池任务,调用
Future.cancel(true)或executor.shutdownNow() - 对 Handler,调用
removeCallbacksAndMessages(null) - 对 View 回调,使用
view.removeCallbacks(runnable) - 对事件总线,显式调用
unregister(this)
更可靠的预防方式
从设计源头减少隐患比事后补救更高效:
- 优先用静态内部类 + 显式传参替代非静态内部类
- 不依赖
this访问外部状态时,直接改用顶层独立类 - 必须跨生命周期通信时,在内部类中持
WeakReference<outer></outer>,每次访问前判空 - 避免在内部类中直接读写外部类的非 static 字段;改用接口回调 + 弱引用传参
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











