清理 JavaFX 属性侦听器和绑定(内存泄漏) [英] Clean JavaFX property listeners and bindings (memory leaks)

查看:33
本文介绍了清理 JavaFX 属性侦听器和绑定(内存泄漏)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I haven't found a simple answer for these two questions:

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

  1. do I have to remove a listener before deleting the property instance (the listener is not used anywhere else)?

BooleanProperty bool = new SimpleBooleanProperty();
bool.addListener(myListener);
bool.removeListener(myListener); // is it necessary to do this?
bool = null;

  • 在删除属性实例之前,我是否必须取消绑定一个单向有界属性?

  • do I have to unbind a uni-directional bounded property before deleting the property instance?

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

  • 推荐答案

    案例 1

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

    Case 1

    Given that myListener "is not used anywhere else" and therefore I assume, a [method-] local variable, the answer is no. In the general case though, the answer is mostly a no but can sometimes be a yes.

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

    As long as myListener is strongly reachable, then it will never become eligible for finalization and it will continue to consume memory. For example, this would be the case if myListener is a "normally" declared static variable (*all "normal" references in Java are strong references*). However, if myListener is a local variable, then the object will not be reachable anymore after the return of the current method call and bool.removeListener(myListener) is a bit meaningless over-engineering. Both the observer and the Observable goes out of scope and will eventually be finalized. A quote from my own blog post about this answer might paint a better picture:

    盒子是否知道里面的猫并不重要,如果你把盒子扔进海里.如果无法访问该框,则猫.

    It doesn’t matter if the box know about the cat inside of it, if you throw the box into the ocean. If the box isn't reachable, nor is the cat.

    理论

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

    如果某个对象可以被某个线程访问,则该对象是强可达的无需遍历任何引用对象.一个新创建的对象是创建它的线程可以强访问.[..] 一个对象是弱可达,如果它[不]强[..]可达但可以通过遍历弱引用达到.当弱引用一个弱可达对象被清除,该对象有资格获得定稿.

    An object is strongly reachable if it can be reached by some thread without traversing any reference objects. A newly-created object is strongly reachable by the thread that created it. [..] An object is weakly reachable if it is [not] strongly [..] reachable but can be reached by traversing a weak reference. When the weak references to a weakly-reachable object are cleared, the object becomes eligible for finalization.

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

    In the case of static variables, these will always be accessible as long as the class is loaded, thus reachable. If we didn't want a static reference to be the one that hinder the garbage collector to do his job, then we could declare the variable to use a WeakReference instead. JavaDoc says:

    弱引用对象 [..] 不会阻止它们的引用对象被制作完成,完成,然后回收.[..] 假设垃圾收集器在某个时间点确定一个对象弱可达.到时候它会自动清除所有弱的对该对象的引用 [..].同时它将声明所有将要终结的以前弱可达对象的数量.

    Weak reference objects [..] do not prevent their referents from being made finalizable, finalized, and then reclaimed. [..] Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object [..]. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable.

    显式管理

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

    Explicit management

    For illustration, let's assume that we write a JavaFX space simulation game. Whenever an Observable planet moves into the view of a spaceship observer, the game engine register the spaceship with the planet. It is quite apparent that whenever the planet goes out of view, the game engine should also remove the spaceship as an observer of the planet by using Observable.removeListener(). Otherwise, as the spaceship continues to fly through space, memory will leak. Eventually, the game cannot handle five billion observed planets and it will crash with an OutOfMemoryError.

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

    Do note that for the vast majority of JavaFX listeners and event handlers, their life-cycle is parallel to that of their Observable so the application developer has nothing to worry about. For example, we might construct a TextField and register with the text field's textProperty a listener that validate user input. As long as the text field sticks around, we want the listener to stick around. Sooner or later, the text field is not used anymore and when he is garbage collected, the validation listener is also garbage collected.

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

    To continue on the space simulation example, assume that our game has limited multiplayer support and all the players need to observe each other. Perhaps each player keep a local score board of kill metrics or perhaps they need to observe broadcasted chat messages. The reason is not the important point here. What would happen when a player quit the game? Clearly, if the listeners are not explicitly managed (removed), then the player who quit will not become eligible for finalization. The other player's will keep a strong reference to the offline player. Explicit removal of the listeners would still be a valid option and probably the most preferred choice for our game, but let's say that it feels a bit obtrusive and we want to find a more slick solution.

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

    We know that the game engine keep strong references to all players online, for as long as they are online. So we want the spaceships to listen for changes or events of each other only for as long as the game engine keep the strong references. If you read the "theory" section, then surely a WeakReference sounds like a solution.

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

    However, just wrapping something in a WeakReference is not the entire solution. It seldom is. It is true that when the last strong reference to the "referent" is set to null or otherwise become unreachable, the referent will be eligible for garbage collection (assuming that the referent cannot be reached using a SoftReference). But the WeakReference is still hanging around. The application developer need to add some plumbing so that the WeakReference itself is removed from the data structure he was put in. If not, then we might have reduced the severity of the memory leak but a memory leak will still be present because dynamically added weak references consume memory too.

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

    Lucky for us, JavaFX added interface WeakListener and class WeakEventHandler as a mechanism for "automatic removal". The constructors of all related classes accept the real listener/handler as provided by client code, but they store the listener/handler using a weak reference.

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

    If you look at the JavaDoc of WeakEventHandler, you'll notice that the class implement EventHandler, so the WeakEventHandler can be used wherever an EventHandler is expected. Likewise, a known implementation of a WeakListener can be used wherever an InvalidationListener or a ChangeListener is expected.

    如果您查看WeakEventHandler 的源代码,您会注意到该类基本上只是一个包装器.当他的指示对象(真正的事件处理程序)被垃圾收集时,WeakEventHandlerWeakEventHandler.handle() 被调用.WeakEventHandler 不知道他连接了哪个对象,即使他知道,事件处理程序的删除也不是同构的.但是,所有已知的 WeakListener 实现类都具有竞争优势.当它们的回调被调用时,它们会被隐式或显式地提供一个对它们注册的 Observable 的引用.因此,当 WeakListener 的指示对象被垃圾收集时,最终 WeakListener 实现将确保 WeakListener 本身从 中删除可观察.

    If you look into the source code of WeakEventHandler, you'll notice that the class is basically only a wrapper. When his referent (the real event handler) is garbage collected, the WeakEventHandler "stop working" by not doing anything at all when WeakEventHandler.handle() is called. The WeakEventHandler doesn't know about which object he has been hooked up with, and even if he did, the removal of an event handler is not homogeneous. All known implementing classes of WeakListener has a competitive advantage though. When their callbacks are invoked, they are implicitly or explicitly provided a reference to the Observable they are registered with. So when the referent of a WeakListener is garbage collected, eventually the WeakListener implementation will make sure that the WeakListener itself is removed from the Observable.

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

    If it is isn't already clear, the solution for our space simulation game would be to let the game engine use strong references to all online spaceships. When a spaceship goes online, all other online spaceships are registered with the new player using a weak listener such as WeakInvalidationListener. When a player goes offline, the game engine remove his strong reference to the player and the player will become eligible for garbage collection. The game engine doesn't have to bother about explicit removal of the offline player as a listener of the other players.

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

    No. To better understand what I'll say next, please read my case 1 answer first.

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

    BooleanPropertyBase store a strong reference to otherBool. This in itself does not cause otherBool to always be reachable and thus potentially cause a memory leak. When bool becomes unreachable, then so do all its stored references (assuming they are not stored anywhere else).

    BooleanPropertyBase 也可以通过将自身添加为绑定到的属性的 Observer 来工作.但是,它是通过将自身包装在一个类中来实现的,该类的工作方式几乎与我的案例 1 答案中描述的 WeakListener 完全相同.因此,一旦您将 bool 设为无效,从 otherBool 中删除它只是时间问题.

    BooleanPropertyBase also works by adding itself as an Observer of the property you bind it to. However, it does so by wrapping itself in a class that works almost exactly like the WeakListeners described in my case 1 answer. So once you nullify bool, it will be only a matter of time before it is removed from otherBool.

    这篇关于清理 JavaFX 属性侦听器和绑定(内存泄漏)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆