想象一下这个示例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实例更长。
我担心我可能会在这里创建垃圾收集器问题。什么是最佳做法?
答案 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
}