监听器和回调未注销必然导致内存泄漏,因强引用链将短生命周期对象(如activity)钉在堆中;view、mediaplayer、eventbus等长生命周期对象隐式持有其强引用,使ondestroy后gc无法回收,引发级联式堆积。

监听器和回调未注销会直接导致内存泄漏,核心原因是强引用链被意外钉住,让本该被回收的短生命周期对象(如 Activity、Fragment、Service)一直存活在堆中。
强引用链把 Activity “锁死”了
很多监听注册操作会隐式持有调用方的强引用。例如:
-
view.setOnClickListener(this)→ View 持有 Activity 的强引用 -
mediaPlayer.addListener(this)→ MediaPlayer 持有 Activity 的强引用 -
eventBus.register(this)→ EventBus 静态容器持有 Activity 的强引用
一旦 Activity 执行完 onDestroy(),只要这些监听器还挂在长生命周期对象(系统组件、单例、静态集合)上,GC 就无法回收它——Activity 及其持有的所有视图、Bitmap、数据缓存全留在内存里。
跨生命周期残留最危险
不是“可能泄漏”,而是几乎必然泄漏,尤其在以下场景:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- Fragment 中给全局播放器注册监听,Fragment 销毁了,监听还在
- Dialog 内注册
ViewTreeObserver.addOnGlobalLayoutListener,Dialog dismiss 后回调仍在触发 - 网络请求成功后回调里更新 UI,但 Activity 已 finish,回调中的
this仍被 OkHttp 或 RxJava 持有
观察者模式里的“单向绑定”陷阱
手写或自定义的观察者实现常只做 attach 不做 detach:
- Subject 用
ArrayList<observer></observer>存着所有观察者 - Observer 是匿名内部类或 lambda,隐式持有 Activity 实例
- Subject 是静态单例或长期存活的服务,Observer 就永远不释放
结果就是 Observer 拖着 Activity,Activity 拖着整个 View 树和资源,形成级联式堆积。
闭包和委托也会悄悄锁住上下文
不只是 Java,在 Kotlin、C#、JS 中同样存在类似风险:
- Kotlin lambda 默认持有
this,lifecycleScope.launch { … }中若 this 是 Activity,协程未结束前 Activity 就无法回收 - C# 事件
obj.Event += Handler,委托对象同时持_target(实例)和_methodPtr,不-=就一直强引用 - JS
addEventListener回调用了闭包,会把外层作用域所有变量(包括大数组、Vue 实例)一并锁住
这些都不是语法错误,而是设计疏忽——注册了,却忘了清理。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










