监听器或回调未注销是Java高频内存泄漏根源,需严格成对注册/注销、避免匿名类、用方法引用或变量复用、结合弱引用或框架防护,并在销毁时验证监听器清零。

监听器或回调未注销,是Java中非常隐蔽但高频的内存泄漏根源。它不报错、不崩溃,只悄悄拖慢应用、抬高内存水位,直到某天突然OOM。改法不难,关键在“成对”和“稳定”两个词。
注册与注销必须严格成对
只要写了 addXxxListener 或类似注册逻辑,就必须在对象生命周期结束前,明确写出对应的 removeXxxListener。不能靠“后面再补”,也不能只在测试路径里加,而生产路径漏掉。
- Android中:Activity 在 onDestroy()、Fragment 在 onDestroyView() 或 onDetach()、Service 在 onDestroy() 中执行注销
- Swing/JavaFX:窗口关闭时,在 dispose() 或 WindowEvent.WINDOW_CLOSING 回调里清理
- Spring @EventListener:若监听器定义在 prototype Bean 上,必须配合 @PreDestroy 方法或实现 DisposableBean 接口来解绑
别用匿名内部类或临时 Lambda 直接注册
像 button.addActionListener(e -> handleClick()) 或 new ActionListener() { ... } 每次都会生成新实例,remove 时无法匹配原始引用——等于白注册、假注销。
- 改用方法引用:
button.addActionListener(this::handleClick),注销时写button.removeActionListener(this::handleClick) - 或提前声明监听器变量:
private final ActionListener clickListener = e -> ...;,注册和注销都用这个变量
用弱引用或框架防护机制兜底
不是所有场景都能100%保证注销执行(比如异常提前退出、进程被杀)。这时可借助更健壮的设计降低风险:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 自定义事件总线中,监听器列表建议用 WeakReference
包装,让GC能自然回收目标对象 - Guava EventBus:注册时保存
eventBus.register(this)返回值,销毁时调用eventBus.unregister(this) - RxJava:订阅后务必保存 Disposable,并在合适时机调用 dispose() 或 compositeDisposable.clear()
- OkHttp 拦截器、广播接收器等同理:registerReceiver() 后必须 unregisterReceiver()
上线前加一行验证逻辑
在销毁方法末尾加一句日志或断言,确认监听器数量归零:
- 例如打印
Log.d("Leak", "listeners.size() = " + listeners.size()) - 开发阶段可加
if (listeners.size() != 0) throw new IllegalStateException("Listeners not cleared")
简单但有效,能快速暴露遗漏点。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










