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

时间:2008-10-01 14:22:59

标签: 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实例更长。

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

12 个答案:

答案 0 :(得分:17)

参考图中有一个循环。引用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));
    }
}

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

答案 1 :(得分:5)

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:3)

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

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

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

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

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

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

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

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

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

当你编写包含监听器的类

时,这当然有效

答案 10 :(得分:0)

基于@Alexander关于将自己当作听众的说法:

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

时自行删除

答案 11 :(得分:0)

我刚刚发现了一个巨大的内存泄漏,所以我打算将创建泄漏的代码称为错误,并且我的修复不会因为正确而泄漏。

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

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
}