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

Jer*_*emy 29 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() {}
        }
 }
Run Code Online (Sandbox Code Playgroud)

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

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

jan*_*anm 17

参考图中有一个循环.引用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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 通过匿名的Listener子类,它包含对包含B实例的引用. (2认同)
  • 声明:"a.addListener(new Listener(){void listen(){}})"创建一个B的匿名内部类,它具有对B的引用.A通过内部类的实例引用B. (2认同)
  • 卫生署!忘记了anon类确实引用了它创建的实例.实时和学习... (2认同)

wor*_*ad3 5

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


Jer*_*emy 1

我刚刚发现了一个巨大的内存泄漏,所以我将调用创建泄漏的代码是错误的,而我的修复不会泄漏是正确的

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

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
}
Run Code Online (Sandbox Code Playgroud)

显然,这很糟糕。许多 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
}
Run Code Online (Sandbox Code Playgroud)