温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Java观察者模式的内存泄漏问题

发布时间:2026-01-02 05:52:03 来源:亿速云 阅读:134 作者:小樊 栏目:编程语言

成因与典型场景

  • 被观察者通常以强引用持有观察者列表,若未在合适的生命周期里调用removeObserver/注销,观察者会连同其闭包(如Activity/Fragment中的匿名内部类)一起被长期引用,导致无法被GC回收,形成泄漏。这在Android等基于生命周期的场景尤为常见。
  • 使用静态观察者或单例持有观察者,会将其生命周期拉长到与类加载器一致,极易引发泄漏。
  • 采用弱引用保存观察者能避免强引用导致的泄漏,但弱引用在GC后变为null,若不及时清理引用队列或列表中的“空壳”条目,会造成列表膨胀与遍历成本上升,甚至逻辑误判(例如误以为仍“注册着”)。
  • 异步通知或事件总线若未做好取消订阅,也会让观察者被事件源意外延长寿命。

预防与最佳实践

  • 明确生命周期边界:在对象销毁时(如AndroidonDestroyonStop)务必调用deleteObserver/removeObserver;对长期运行对象,提供显式的unsubscribe方法并在使用完后立即调用。
  • 优先使用弱引用或弱集合:将观察者以WeakReference存入列表,或在键侧使用WeakHashMap,并在遍历时清理ref.get() == null的条目,避免“幽灵引用”堆积。
  • 避免静态观察者与长生命周期容器持有短期对象;必要时引入弱引用事件总线的自动清理机制。
  • 选择并发安全的容器:如CopyOnWriteArrayList用于多线程注册/注销与遍历,降低迭代时加锁与ConcurrentModificationException风险。
  • 防止循环引用:观察者与可销毁对象互相持有时要设计清理路径,或在通知阶段使用弱引用断开链路。
  • 持续监测:上线前后结合VisualVMEclipse MATJProfilerYourKitJFR/JMC做堆转储与分配追踪,观察对象生命周期与引用链。

代码示例

  • 弱引用持有 + 遍历清理
import java.lang.ref.WeakReference;
import java.util.*;

class WeakObserverList<T> {
    private final List<WeakReference<T>> refs = new ArrayList<>();

    public void add(T observer) {
        if (observer == null) return;
        refs.add(new WeakReference<>(observer));
    }

    public void remove(T observer) {
        refs.removeIf(ref -> ref.get() == null || ref.get() == observer);
    }

    public void forEach(java.util.function.Consumer<T> action) {
        refs.removeIf(ref -> {
            T o = ref.get();
            if (o == null) return true; // GC 回收后清理
            action.accept(o);
            return false;
        });
    }

    public int size() { return (int) refs.stream().filter(r -> r.get() != null).count(); }
}
  • 生命周期解绑示例(Android)
@Override
protected void onDestroy() {
    super.onDestroy();
    if (observable != null && observer != null) {
        observable.deleteObserver(observer); // 或 eventBus.unregister(this);
    }
    observable = null;
    observer = null;
}
  • 并发安全注册/注销(桌面/服务端)
import java.util.concurrent.CopyOnWriteArrayList;

class Subject {
    private final CopyOnWriteArrayList<Observer> observers = new CopyOnWriteArrayList<>();

    void register(Observer o) { if (o != null) observers.addIfAbsent(o); }
    void unregister(Observer o) { observers.remove(o); }

    void notifyObservers() {
        for (Observer o : observers) o.update();
    }
}
  • 使用 WeakHashMap 的“弱键”场景
// 键为弱引用:当 key 不再被强引用持有时,条目可被回收
Map<Key, Value> weakKeyMap = new WeakHashMap<>();

上述做法分别体现了:弱引用避免强引用泄漏、及时注销生命周期、并发安全容器与弱键集合的使用。

排查与定位

  • 观察症状:进程内存占用随时间单调上升Full GC 频繁但回收效果差、最终出现OutOfMemoryError
  • 采集数据:用JConsole/VisualVM观察堆与 GC;在发生 OOM 时自动或手动抓取堆转储
  • 分析链路:用Eclipse MAT查看Dominator TreeGC Roots引用链,定位仍被持有的观察者及其外部闭包;用JProfiler/YourKit做分配热点与对象生命周期追踪;在生产环境可用JFR/JMC低开销采集并回放分析。
向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI