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

时间:2013-01-28 08:37:04

标签: binding memory-leaks listener javafx

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

  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;
    

1 个答案:

答案 0 :(得分:30)

案例1

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

只要myListener可以很容易地访问,它就永远不会有资格进行最终确定,并且它将继续消耗内存。例如,如果myListener是"通常"声明static变量(* all" normal" Java中的引用是引用*)。但是,如果myListener是局部变量,那么在返回当前方法调用之后对象将不再可访问,并且bool.removeListener(myListener)过度工程有点无意义。观察者和Observable都超出范围,最终将最终确定。我自己blog post关于这个答案的引用可能会描绘出更好的图景:

  

如果你知道盒子里面的猫是否知道它并不重要   把箱子扔进大海。如果盒子不可以到达,也不是   猫。

理论

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

  

如果某个线程可以访问某个对象,则该对象很容易访问   不遍历任何引用对象。新创建的对象是   由创建它的线程强烈访问。 [..]一个对象是   如果[不]强烈[...]可达,则可以弱到达但可以   通过遍历弱参考来达到。弱者引用时   清除弱可达对象,该对象符合条件   定稿。

对于静态变量,只要加载了类,就可以访问它们,因此可以访问。如果我们不希望静态引用成为妨碍垃圾收集器完成其工作的引用,那么我们可以声明该变量使用WeakReference代替。 JavaDoc说:

  

弱引用对象[..]不会阻止它们的引用   最终确定,最终确定,然后回收。 [..]假设那个   垃圾收集器在某个时间点确定一个对象   很难到达。那时它会原子地清除所有弱者   对该对象的引用[..]。同时它将宣布所有   可以最终确定以前可以到达的弱对象。

明确管理

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

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

自动管理

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

我们知道,只要在线,游戏引擎就会对所有在线玩家提供强有力的参考。因此,只要游戏引擎保留强引用,我们就希望宇宙飞船只能监听彼此的变化或事件。如果你阅读"理论"部分,然后肯定WeakReference听起来像一个解决方案。

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

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

如果查看WeakEventHandler的JavaDoc,您会注意到类实现EventHandler,因此可以在期望EventHandler的任何地方使用WeakEventHandler。同样,WeakListener的已知实现可以在预期InvalidationListenerChangeListener的任何地方使用。

如果你查看WeakEventHandler的源代码,你会发现该类基本上只是一个包装器。当他的指示物(真实事件处理程序)被垃圾收集时,WeakEventHandler"停止工作"调用WeakEventHandler.handle()时根本不做任何事情。 WeakEventHandler并不知道他与哪个对象联系在一起,即使他这样做了,事件处理程序的删除也不是同质的。所有已知的WeakListener实施类都具有竞争优势。当调用它们的回调时,它们会隐式或显式地提供对它们注册的Observable的引用。因此,当WeakListener的引用被垃圾收集时,WeakListener实现最终将确保从WeakListener中删除Observable本身。

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

案例2

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

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

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