22

我还没有找到这两个问题的简单答案:

  1. 是否必须在删除属性实例之前删除侦听器(侦听器不在其他任何地方使用)?

    BooleanProperty bool = new SimpleBooleanProperty();
    bool.addListener(myListener);
    bool.removeListener(myListener); // is it necessary to do this?
    bool = null;
    
  2. 在删除属性实例之前,我是否必须取消绑定单向有界属性?

    BooleanProperty bool = new SimpleBooleanProperty();
    bool.bind(otherBool);
    bool.unbind(); // is it necessary to do this?
    bool = null;
    
4

2 回答 2

31

情况1

鉴于myListener“未在其他任何地方使用”,因此我假设是一个 [method-] 局部变量,答案是否定的。但在一般情况下,答案大多是no但有时可能是yes

只要myListener是强可达的,那么它就永远不会有资格进行终结,它将继续消耗内存。例如,如果myListener是“正常”声明的static变量(*Java 中的所有“正常”引用都是引用*),就会出现这种情况。但是,如果myListener是局部变量,那么在当前方法调用返回后,该对象将不再可达,bool.removeListener(myListener)有点无意义的过度工程。观察者和观察者Observable都超出了范围,最终将被最终确定。我自己的博客文章中关于这个答案的引述可能会描绘出更好的画面:

盒子是否知道里面的猫并不重要,如果你把盒子扔到海里。如果盒子够不到,猫也够不到。

理论

为了完全理解这里的情况,我们必须提醒自己 Java 对象的生命周期(source):

如果某个对象可以被某个线程访问而无需遍历任何引用对象,则该对象是强可达的。创建它的线程可以强烈访问新创建的对象。[..] 一个对象是弱可达的,如果它[不是] 强[..] 可达但可以通过遍历一个弱引用来达到。当对弱可达对象的弱引用被清除时,该对象就有资格进行终结。

在静态变量的情况下,只要类被加载,它们就总​​是可以访问的,因此是可以访问的。如果我们不希望静态引用成为阻碍垃圾收集器完成工作的对象,那么我们可以声明变量以使用 aWeakReference代替。JavaDoc 说:

弱引用对象 [..] 不会阻止它们的引用对象被最终化、最终化,然后被回收。[..]假设垃圾收集器在某个时间点确定一个对象是弱可达的。那时它将自动清除对该对象的所有弱引用 [..]。同时它将声明所有以前的弱可达对象都是可终结的。

显式管理

为了说明,假设我们编写了一个 JavaFX 空间模拟游戏。每当Observable一颗行星进入宇宙飞船观察者的视野时,游戏引擎就会向该行星注册宇宙飞船。很明显,每当行星离开视野时,游戏引擎也应该通过使用Observable.removeListener(). 否则,随着飞船继续在太空中飞行,内存就会泄漏。最终,游戏无法处理 50 亿颗观测到的行星,并且会以OutOfMemoryError.

请注意,对于绝大多数 JavaFX 侦听器和事件处理程序,它们的生命周期与它们的生命周期是平行的,Observable因此应用程序开发人员无需担心。例如,我们可以构造一个TextField并在文本字段中注册textProperty一个验证用户输入的侦听器。只要文本字段存在,我们就希望听众持续存在。迟早,文本字段不再使用,当他被垃圾收集时,验证侦听器也被垃圾收集。

自动管理

继续空间模拟示例,假设我们的游戏对多人游戏的支持有限,并且所有玩家都需要互相观察。也许每个玩家都有一个本地的击杀指标计分板,或者他们可能需要观察广播的聊天消息。原因不是这里的重点。当玩家退出游戏时会发生什么?显然,如果监听器没有被明确管理(移除),那么退出的玩家将没有资格获得最终确定。其他玩家将保持对离线玩家的强烈参考。显式删除侦听器仍然是一个有效的选项,并且可能是我们游戏的最首选选项,但假设它感觉有点突兀,我们希望找到一个更巧妙的解决方案。

我们知道,只要他们在线,游戏引擎就会对所有在线玩家保持强烈的参考。所以我们希望宇宙飞船只在游戏引擎保持强引用的情况下监听彼此的变化或事件。如果您阅读“理论”部分,那么WeakReference听起来肯定是一个解决方案。

然而,仅仅在 WeakReference 中包装一些东西并不是完整的解决方案。它很少是。确实,当对“referent”的最后一个强引用被设置为null或变得不可访问时,该referent 将有资格进行垃圾收集(假设无法使用 a 来SoftReference访问该referent )。但是 WeakReference 仍然存在。应用程序开发人员需要添加一些管道,以便将 WeakReference 本身从他放入的数据结构中删除。如果没有,那么我们可能已经降低了内存泄漏的严重性,但内存泄漏仍然存在,因为动态添加了弱引用也会消耗内存。

幸运的是,JavaFX 添加了接口WeakListener和类WeakEventHandler作为“自动删除”的机制。所有相关类的构造函数都接受客户端代码提供的真正的侦听器/处理程序,但它们使用弱引用存储侦听器/处理程序。

如果您查看 的 JavaDoc WeakEventHandler,您会注意到该类实现了EventHandler,因此 WeakEventHandler 可以在任何需要 EventHandler 的地方使用。同样,a 的已知实现WeakListener可以在任何预期InvalidationListenera 或 a的地方使用。ChangeListener

如果您查看 的源代码WeakEventHandler,您会注意到该类基本上只是一个包装器。当他的所指对象(真正的事件处理程序)被垃圾收集时,调用WeakEventHandler时根本不做任何事情来“停止工作” WeakEventHandler.handle()。TheWeakEventHandler不知道他与哪个对象挂钩,即使他知道,事件处理程序的删除也不是同质的。尽管如此,所有已知的实现类都WeakListener具有竞争优势。当它们的回调被调用时,它们被隐式或显式地提供了对Observable它们注册的引用。因此,当 a 的所指对象WeakListener被垃圾回收时,最终WeakListener实现将确保将其WeakListener自身从Observable.

如果还不清楚,我们的太空模拟游戏的解决方案是让游戏引擎使用对所有在线宇宙飞船的强引用。当一艘宇宙飞船上线时,所有其他在线宇宙飞船都会使用弱监听器向新玩家注册,例如WeakInvalidationListener. 当玩家下线时,游戏引擎会删除他对玩家的强引用,玩家将有资格进行垃圾回收。游戏引擎不必费心将离线玩家作为其他玩家的侦听器显式移除。

案例2

不。为了更好地理解我接下来要说的内容,请先阅读我的案例 1 答案。

BooleanPropertyBase存储对otherBool. 这本身并不会导致otherBool始终可访问,因此可能会导致内存泄漏。当bool变得无法访问时,其所有存储的引用也是如此(假设它们没有存储在其他任何地方)。

BooleanPropertyBase也可以通过将自身添加为Observer您绑定到的属性的一个来工作。但是,它通过将自身包装在一个几乎与WeakListener我的案例 1 答案中描述的 s 完全相同的类中来实现这一点。所以一旦你作废bool,它被删除只是时间问题otherBool

于 2014-07-15T13:05:14.353 回答
7

我完全同意案例 1的答案,但案例 2有点棘手。The bool.unbind()打电话是必要的。如果省略,它确实会导致小的内存泄漏。

如果运行以下循环,应用程序最终将耗尽内存。

BooleanProperty p1 = new SimpleBooleanProperty();
while(true) {
    BooleanProperty p2 = new SimpleBooleanProperty();
    p2.bind(p1)
}

BooleanPropertyBase 本质上不使用真正的 WeakListener(WeakListener接口的实现),它使用的是半生不熟的解决方案。所有“p2”实例最终都会被垃圾回收,但是对于每个“p2”,持有空 WeakReference 的侦听器将永远保留在内存中。这同样适用于所有属性,不仅是 BooleanPropertyBase。 这里有详细解释,他们说它在 Java 9 中已修复。

在大多数情况下,您不会注意到这种内存泄漏,因为它只为每个未解除绑定的绑定留下了几十个字节。但在某些情况下,它给我带来了真正的麻烦。一个很好的例子是经常更新的表格的表格单元格。然后这些细胞会一直重新绑定到不同的属性,这些残留在记忆中的东西会迅速积累。

于 2015-09-19T16:36:57.613 回答