清理JavaFX属性侦听器和绑定(内存泄漏)

dpe*_*sek 21 binding memory-leaks javafx listener

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

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

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

    BooleanProperty bool = new SimpleBooleanProperty();
    bool.bind(otherBool);
    bool.unbind(); // is it necessary to do this?
    bool = null;
    
    Run Code Online (Sandbox Code Playgroud)

Mar*_*son 30

情况1

鉴于myListener"其他地方没有使用",因此我假设一个[method-]局部变量,答案是否定的.但在一般情况下,答案主要是否定,但有时可能是肯定的.

只要myListener是强可达的,它就永远不会有资格进行最终化,并且它将继续消耗内存.例如,如果myListener是"通常"声明的static变量(*Java中的所有"普通"引用都是引用*),则会出现这种情况.但是,如果myListener是局部变量,则在返回当前方法调用之后,该对象将不再可访问,并且bool.removeListener(myListener)过度工程有点无意义.观察者和Observable范围都超出范围,最终将最终确定.我自己的博客文章中有关此答案的引用可能会描绘出更好的图景:

如果你将盒子扔进海里,盒子是否知道它里面的猫并不重要.如果盒子不可达,猫也不可以.

理论

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

如果某个对象可以在不遍历任何引用对象的情况下到达某个对象,则该对象是强可访问的.新创建的对象可由创建它的线程强烈访问.[..]如果一个对象[不]强[...]可达,但是可以通过遍历弱引用来到达,则该对象是弱可达的.当清除对弱可达对象的弱引用时,该对象将有资格进行最终确定.

在静态变量的情况下,只要加载了类,就可以访问它们,因此可以访问.如果我们不希望静态引用成为阻碍垃圾收集器完成其工作的引用,那么我们可以声明变量使用WeakReference而不是.JavaDoc说:

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

明确的管理

为了说明,我们假设我们编写了一个JavaFX空间模拟游戏.每当Observable一颗行星进入宇宙飞船观察者的视野时,游戏引擎就会将宇宙飞船记录在地球上.很明显,每当行星离开视线时,游戏引擎也应该通过使用来移除宇宙飞船作为行星的观察者Observable.removeListener().否则,当宇宙飞船继续飞越太空时,记忆将会泄漏.最终,游戏无法处理50亿个观测到的行星,并且它会崩溃OutOfMemoryError.

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

自动管理

继续进行空间模拟示例,假设我们的游戏具有有限的多人游戏支持,并且所有玩家都需要相互观察.也许每个玩家都有一个杀戮指标的本地记分板,或者他们需要观察广播的聊天消息.原因不是重点.当玩家退出游戏时会发生什么?显然,如果未明确管理(删除)侦听器,则退出的玩家将无法完成最终确定.其他玩家将对线下玩家提供强有力的参考.明确删除听众仍然是一个有效的选择,可能是我们游戏的首选,但让我们说它感觉有点突兀,我们想找到一个更光滑的解决方案.

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

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

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

如果查看JavaDoc WeakEventHandler,您会注意到类实现EventHandler,因此可以在期望EventHandler的任何地方使用WeakEventHandler.同样地,一个公知的实施方式WeakListener可用于任何一个InvalidationListener或一个ChangeListener被期待.

如果你查看源代码WeakEventHandler,你会发现该类基本上只是一个包装器.当他的指示物(真实事件处理程序)被垃圾收集时,WeakEventHandler通过不执行任何操作来"停止工作" WeakEventHandler.handle()被调用.在WeakEventHandler不知道他已经迷上了,即使他这样做了,拆除的事件处理程序的不均质物体.所有已知的实施类都WeakListener具有竞争优势.当调用它们的回调时,它们会隐式或显式地提供对Observable它们注册的引用.因此,当a的引用WeakListener被垃圾收集时,最终WeakListener实现将确保WeakListener自己从中删除Observable.

如果尚不清楚,我们的太空模拟游戏的解决方案是让游戏引擎使用对所有在线太空船的强引用.当宇宙飞船上线时,所有其他在线宇宙飞船都使用弱听众注册新玩家WeakInvalidationListener.当玩家离线时,游戏引擎会删除他对玩家的强烈引用,玩家将有资格进行垃圾回收.游戏引擎不必担心明确移除作为其他玩家的倾听者的离线玩家.

案例2

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

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

BooleanPropertyBase也可以将自己添加为Observer绑定它的属性.然而,它是通过将自己包装在一个类似于WeakListener我的案例1答案中描述的类的类来实现的.因此,一旦您取消bool,它将被删除只是时间问题otherBool.

  • 说实话,我不明白这个答案.案例1 - 没有或是.案例2 - 不,见案例1. (2认同)

Jan*_*rek 7

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

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

BooleanProperty p1 = new SimpleBooleanProperty();
while(true) {
    BooleanProperty p2 = new SimpleBooleanProperty();
    p2.bind(p1)
}
Run Code Online (Sandbox Code Playgroud)

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

在大多数情况下,您不会注意到此内存泄漏,因为它为每个尚未绑定的绑定只留下几十个字节.但在某些情况下,它给我带来了真正的麻烦.一个很好的例子是经常更新的表的表格单元格.然后,单元格一直重新绑定到不同的属性,并且内存中的这些剩余部分会快速累积.