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

时间:2020-03-06 14:58:42  来源:igfitidea点击:

想象一下这个示例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被垃圾收集时,它也应该允许A被垃圾收集,因此A中的所有引用也应被垃圾收集。我们无需显式删除A中的引用。

我不知道任何有关建议是否可以使垃圾收集器更有效地运行的数据,但是什么时候值得打扰,但我对此很感兴趣。

A确实将通过匿名实例使B保持活动状态。

但是我不会重写finalize来解决这个问题,而是使用不会使B存活的静态内部类。

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

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

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

声明:"假设A实例也将与其他一些对象共享,并且将比B实例寿命更长。"是错的。唯一会发生的方法是,将侦听器从终结器以外的其他位置显式删除。如果传递了对A的引用,这将意味着对B的引用,并且不会对B进行垃圾回收,因为存在对A-B循环的外部引用。

进一步更新:

如果要中断循环,并且不需要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));
    }
}

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

如果我们已将B添加为A的侦听器,并且A的目的是使B失效,那么B上的finalize调用将永远不会被调用,因为A内有B的一个实例,因此它将永远不会被垃圾回收。我们可以通过将对B的引用存储为WeakReference来解决此问题(在车库回收期间不将其视为引用),但是最好在不再需要B时从B显式注销B更好。

通常,建议在Java中不要在Java中使用finalize方法,因为我们无法确定何时调用该方法,也不能使用它从另一个类中注销自己。

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

回到问题,侦听器是一个独立的对象,具有自己的生命周期,并且将在收集所有引用该对象的对象之后或者没有其他对象指向它时收集该对象。这很好。所以不,我们不必重写finalize。

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

当A被销毁时,它对B的匿名引用也将破坏B,从而为B被释放开辟道路。

但是由于B拥有对A的引用,所以这永远不会发生。如果A调用了一个侦听器,这似乎是一个设计问题,为什么我们还需要B保留对A的引用?如有必要,为什么不将发出呼叫的A传递给侦听器?

A的确可以防止B在我们使用标准引用存储侦听器的过程中被垃圾回收。或者,当我们维护侦听器列表而不是定义
new ArrayList <ListenerType>();
你可以做类似的事情
new ArrayList <WeakReference <ListenerType >>();

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

这仅在我们编写包含侦听器的类时才有效

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;
}

因为那时我真的想知道那个匿名类会发生什么...。

在情况下,唯一的垃圾收集"问题"是,当存在对A共享实例的硬引用时,将不会对B实例进行垃圾收集。这就是应该在Java / .NET中进行垃圾收集的方式。现在,如果我们不喜欢B的实例没有被较早地进行垃圾收集这一事实,我们需要问问自己在什么时候希望它们停止侦听A中的事件?找到答案后,我们将知道如何修复设计。

以@Alexander关于将自己删除为侦听器的内容为基础:

除非有令人信服的理由,否则我从我的同事那里了解到的一件事是,无需创建匿名内部侦听器,而是需要将其存储在变量中,请让B实现侦听器,然后B可以删除自身当需要使用a.removeListener(this)

我只是发现了一个巨大的内存泄漏,因此我将导致该泄漏的代码称为错误,而我的未正确泄漏的修复程序也称为错误代码。

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

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<NotLeaky> ownerRef;
        private Singleton eventer;
        NotLeakyListener(NotLeaky owner, Singleton e) {
            ownerRef = new WeakReference<NotLeaky>(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
}