观察者模式泄漏主因是注册后未注销,导致短命对象被长命对象持有而无法gc;需配对remove/unregister、避免静态集合缓存、慎用weakreference并配合referencequeue清理。

观察者模式本身不是问题,问题出在注册之后忘了注销——对象明明不用了,却还被持有引用,GC无法回收,内存就悄悄涨起来了。
监听器没移除是最常见的泄漏点
比如一个Activity或Fragment注册了网络状态监听器,但销毁时没调用removeCallback;或者一个Service向全局事件总线注册了ObserverCallback,生命周期结束却没反注册。这些观察者对象会一直卡在引用链里,连带它们持有的上下文(如Activity)也无法释放。
- GUI组件、Android Activity/Fragment、Spring Bean中注册的事件监听器,都属于“短命对象注册到长命对象”典型场景
- 尤其注意异步回调:线程池任务里注册的观察者,可能比发起方活得更久
- 检查所有
addXXXListener、registerObserver调用,确认对应位置是否有配对的remove或unregister
静态集合缓存加重泄漏风险
如果观察者列表是static的,或者被存在单例、Application、ServletContext这类长生命周期容器里,那注册进去的每个观察者都会被永久持有——哪怕它所属的页面早已关闭。
- 避免用
static List<observercallback></observercallback>管理回调,改用实例变量或依赖注入容器管理生命周期 - 若必须用全局注册表,确保提供按来源清理的能力(例如传入context tag,支持批量清除某模块注册项)
- 参考
CopyOnWriteArrayList可保证遍历时线程安全,但不能解决泄漏,仍需主动清理
用WeakReference缓解强引用僵局
当观察者确实需要弱耦合(比如UI控件监听后台数据变化),可用WeakReference<observercallback></observercallback>包装观察者。一旦原始对象被GC,引用自动失效,不会阻止回收。
- 注意WeakReference本身不防NPE,每次使用前要
get() != null判空 - 不适合需要稳定长期回调的场景(如定时任务监听器),因为随时可能被回收
- 结合
ReferenceQueue可实现自动清理失效引用,避免WeakReference堆积
排查和验证的关键动作
光靠代码审查不够,得看运行时表现。
- 启动应用后反复打开关闭同一页面,用VisualVM或MAT查看该Activity实例数是否持续增长
- 触发一次Full GC后,dump堆内存,筛选疑似泄漏类(如你的ObserverCallback实现类),看GC Roots路径是否指向静态集合或单例
- 在removeCallback逻辑里加日志或断点,确认注销调用真实发生,而不是被异常跳过或条件未满足
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











