是否需要删除Java侦听器? (一般来说)

想象一下这个示例java类:

class A { void addListener(Listener obj); void removeListener(Listener obj); } class B { private A a; B() { a = new A(); a.addListener(new Listener() { void listen() {} } } 

我是否需要向B添加finalize方法以调用a.removeListener? 假设A实例也将与其他一些对象共享,并且将比B实例更长。

我担心我可能会在这里创建一个垃圾收集器问题。 什么是最佳做法?

参考图中有一个循环。 引用B和B引用A.垃圾收集器将检测周期并查看何时没有对A和B的外部引用,然后将收集两者。

试图在这里使用终结器是错误的。 如果B被销毁,则对A的引用也将被删除。


声明:“假设A实例也将与其他一些对象共享,并且将比B实例更长。” 是错的。 唯一的方法是如果从终结器以外的某个地方明确地移除了侦听器。 如果传递对A的引用,那将意味着对B的引用,并且B将不会被垃圾收集,因为存在对AB循环的外部引用。


进一步更新:

如果要中断循环并且不要求B显式删除侦听器,则可以使用WeakReference。 像这样的东西:

 class A { void addListener(Listener obj); void removeListener(Listener obj); } class B { private static class InnerListener implements Listener { private WeakReference m_owner; private WeakReference m_source; InnerListener(B owner, A source) { m_owner = new WeakReference(owner); m_source = new WeakReference(source); } void listen() { // Handling reentrancy on this function left as an excercise. B b = (B)m_owner.get(); if (b == null) { if (m_source != null) { A a = (A) m_source.get(); if (a != null) { a.removeListener(this); m_source = null; } } return; } ... } } private A a; B() { a = new A(); a.addListener(new InnerListener(this, a)); } } 

如果需要跨多个类可以进一步推广。

我对GC的理解是,在调用removeListener方法之前,类A将维护对侦听器的引用,因此它不会成为GC清理的候选者(因此不会调用finalize)。

如果您已将B添加为A的侦听器,并且A意味着比B更长,则B上的最终调用将永远不会被调用,因为在A内部存在B的实例,因此它将永远不会被垃圾收集。 您可以通过在A中存储对B的引用作为WeakReference(在车库收集期间不被视为参考)来解决这个问题,但是当您不再需要时,最好从A中明确注销B.

一般来说,在Java中建议不要在Java中使用finalize方法,因为你永远无法确定它何时被调用,并且你不能使用它从另一个类中注销自己。

您必须来自C ++或人们实现析构函数的其他语言。 在Java中,你不这样做。 除非你真的知道自己在做什么,否则不要覆盖终结。 在10年里,我从来没有这样做,我仍然想不出一个需要我去做的好理由。

回到你的问题,你的监听器是一个独立的对象,它有自己的生命周期,并且会在收集所有引用它的其他对象之后或者没有其他对象指向它时收集。 这非常有效。 所以不,你不必覆盖finalize。

A确实会通过匿名实例保持B存活。

但我不会覆盖finalize来解决这个问题,而是使用一个静态的内部类,它不会让B保持活着。

在您的情况下,唯一的垃圾收集“问题”是,当存在对A的共享实例的硬引用时, B实例将不会被垃圾收集。 这就是垃圾收集应该如何在Java / .NET中工作。 现在,如果你不喜欢B实例之前没有垃圾收集的事实,你需要问自己,你希望他们在什么时候停止收听来自A事件? 一旦得到答案,您就会知道如何修复设计。

A通过创建的匿名类型隐式使用的匿名实例保存对B的引用。 这意味着在调用removeListener之前不会释放B,因此不会调用B的finalize。

当A被摧毁时,它对B的匿名引用也会破坏B打开通往B的方式。

但由于B持有对A的引用,因此这种情况从未发生过。 这似乎是一个设计问题 – 如果A有一个监听器,你为什么还需要B来保持对A的引用? 如果有必要,为什么不将调用的A传递给听众呢?

A如何比B更活跃?:

B和A的使用示例:

 public static main(args) { B myB = new B(); myB = null; } 

我期望的行为:

GC将删除myB,并且在myB实例中仅引用A实例,因此它也将被删除。 与他们分配的所有听众?

你的意思是:

 class B { private A a; B(A a) { this.a = a; a.addListener(new Listener() { void listen() {} } } 

用法:

 public static main(args) { A myA = new A(); B myB = new B(myA); myB = null; } 

因为那时我真的很想知道那个匿名课程会发生什么……

当B被垃圾收集时,它应该允许A被垃圾收集,因此也是A中的任何引用。 您无需在A中显式删除引用。

我不知道你的建议是否会使垃圾收集器更有效地运行的任何数据,当它值得麻烦时,但我有兴趣看到它。

基于@Alexander关于将自己作为听众去除的说法:

除非有一些令人信服的理由不这样做,我从同事那里学到的一件事是,不是制作一个匿名的内部监听器,而是需要将它存储在变量中,使B实现监听器,然后B可以自行移除何时需要使用a.removeListener(this)

在您使用标准引用存储您的侦听器时,确实会使B不被垃圾收集。 或者,当您维护侦听器列表而不是定义新的ArrayList ()时; 你可以做一些像新的ArrayList >();

通过将对象包装在weakReference中,可以防止它延长对象的使用寿命。

当然,如果您正在编写包含侦听器的类,这当然有效

我刚发现一个巨大的内存泄漏,所以我打算调用创建泄漏的代码是错误的 ,我的修复不会泄漏。

这是旧代码:(这是我见过的常见模式)

 class Singleton { static Singleton getInstance() {...} void addListener(Listener listener) {...} void removeListener(Listener listener) {...} } class Leaky { Leaky() { // If the singleton changes the widget we need to know so register a listener Singleton singleton = Singleton.getInstance(); singleton.addListener(new Listener() { void handleEvent() { doSomething(); } }); } void doSomething() {...} } // Elsewhere while (1) { Leaky leaky = new Leaky(); // ... do stuff // leaky falls out of scope } 

显然,这很糟糕。 许多Leaky正在被创建,并且永远不会收集垃圾,因为听众会让他们活着。

这是我修复内存泄漏的替代方案。 这是有效的,因为我只关心对象存在时的事件监听器。 监听器不应该保持对象存活。

 class Singleton { static Singleton getInstance() {...} void addListener(Listener listener) {...} void removeListener(Listener listener) {...} } class NotLeaky { private NotLeakyListener listener; NotLeaky() { // If the singleton changes the widget we need to know so register a listener Singleton singleton = Singleton.getInstance(); listener = new NotLeakyListener(this, singleton); singleton.addListener(listener); } void doSomething() {...} protected void finalize() { try { if (listener != null) listener.dispose(); } finally { super.finalize(); } } private static class NotLeakyListener implements Listener { private WeakReference ownerRef; private Singleton eventer; NotLeakyListener(NotLeaky owner, Singleton e) { ownerRef = new WeakReference(owner); eventer = e; } void dispose() { if (eventer != null) { eventer.removeListener(this); eventer = null; } } void handleEvent() { NotLeaky owner = ownerRef.get(); if (owner == null) { dispose(); } else { owner.doSomething(); } } } } // Elsewhere while (1) { NotLeaky notleaky = new NotLeaky(); // ... do stuff // notleaky falls out of scope }