案例1
假设myListener
“没有在任何其他地方使用”,因此我认为它是一个[方法]局部变量,答案是否定的。然而,在一般情况下,答案通常是否定的,但有时可以是肯定的。
只要myListener
是强可到达的,它就永远不会变得可终结,而且将继续占用内存。例如,如果myListener
是一个“通常”声明的static
变量(在Java中,所有“正常”的引用都是强引用),那么这就是情况。然而,如果myListener
是局部变量,则对象在当前方法调用返回后将不再可访问,bool.removeListener(myListener)
有点毫无意义的过度工程。观察者和Observable
都超出了范围,最终将被终结。从我自己的博客文章中摘取一句话可能更好地说明问题:
理论
为了完全理解这里的情况,我们必须想起一个 Java 对象的生命周期(source):
如果一个对象可以被某个线程访问到而不需要遍历任何引用对象,则该对象是强可达的。新创建的对象由创建它的线程强可达。[...] 如果一个对象不是强可达的,但可以通过遍历弱引用来访问,则该对象是弱可达的。当对弱可达对象的弱引用被清除时,该对象就变得可以进行终结。
如果你把盒子扔进海洋里,那么盒子里有没有猫都无所谓。如果盒子无法被找到,那么猫也是无法被找到的。
在静态变量的情况下,只要类被加载,这些变量就始终可访问,因此是可达的。如果我们不希望静态引用阻碍垃圾回收器的工作,那么我们可以声明变量使用
WeakReference
。JavaDoc中说:
弱引用对象[..]不会阻止其引用对象被标记为可终结、终结和回收。[..]假设垃圾回收器在某个时间点确定一个对象是弱可达的。那时,它将原子性地清除对该对象的所有弱引用[..]同时,它将声明所有以前弱可达的对象为可终结。
显式管理
举个例子,假设我们编写了一个JavaFX太空模拟游戏。每当一个可观察的星球进入飞船观察者的视野时,游戏引擎会将飞船与星球注册。很明显,每当星球离开视野时,游戏引擎也应该使用
Observable.removeListener()
将飞船作为星球的观察者移除。否则,随着飞船继续在太空中飞行,内存将泄漏。最终,游戏无法处理50亿个被观察的星球,并且会因为
OutOfMemoryError
而崩溃。
请注意,对于绝大多数JavaFX监听器和事件处理程序,它们的生命周期与其
Observable
并行,因此应用程序开发人员无需担心。例如,我们可以构造一个{{link1:
TextField
}}并向文本字段的
textProperty
注册一个验证用户输入的监听器。只要文本字段存在,我们希望监听器也存在。早晚,当文本字段不再使用时,它将被垃圾回收,验证监听器也将被垃圾回收。
自动管理
继续以太空模拟为例,假设我们的游戏有限的多人支持,所有玩家都需要观察彼此。也许每个玩家都会保留一个本地的击杀指标积分牌,或者他们需要观察广播的聊天消息。原因并不重要。当一个玩家退出游戏时会发生什么?显然,如果监听器没有被明确管理(删除),那么退出游戏的玩家将无法成为最终化的对象。其他玩家将保持对离线玩家的强引用。明确删除监听器仍然是一个有效的选择,可能是我们游戏中最受欢迎的选择,但是假设它感觉有点突兀,我们想找到更加流畅的解决方案。
我们知道游戏引擎会在玩家在线的时间内一直保持对所有在线玩家的强引用。因此,我们希望飞船只在游戏引擎保持强引用的时间内监听彼此的变化或事件。如果您阅读了“理论”部分,那么肯定会认为弱引用是一个解决方案。
然而,仅仅将某物包装在WeakReference中并不能解决问题。它很少能够解决问题。当对“referent”的最后一个强引用设置为null或无法访问时,参考对象就有资格进行垃圾回收(假设使用
SoftReference
也无法访问参考对象)。但是WeakReference仍然存在。应用程序开发人员需要添加一些管道,以便从他放置的数据结构中删除WeakReference本身。如果没有这样做,则我们可能已经减轻了内存泄漏的严重程度,但由于动态添加的WeakReference也会消耗内存,因此仍然存在内存泄漏问题。
JavaFX为我们提供了接口WeakListener
和类WeakEventHandler
,作为一种“自动删除”的机制。所有相关类的构造函数都接受客户端代码提供的真实监听器/处理程序,但它们使用弱引用存储监听器/处理程序。
如果您查看WeakEventHandler
的JavaDoc,您会注意到该类实现了EventHandler
,因此可以在任何需要EventHandler
的地方使用WeakEventHandler
。同样,已知的WeakListener
的实现可以在任何需要InvalidationListener
或ChangeListener
的地方使用。
如果您查看
WeakEventHandler
的源代码,您会注意到该类实际上只是一个包装器。当它的引用(真正的事件处理程序)被垃圾回收时,
WeakEventHandler
通过在调用
WeakEventHandler.handle()
时不执行任何操作来“停止工作”。
WeakEventHandler
并不知道它已连接到哪个对象,即使它知道,移除事件处理程序也不是均匀的。然而,所有已知的实现
WeakListener
的类都具有竞争优势。当它们的回调被调用时,它们被隐式或显式地提供了一个对其注册的
Observable
的引用。因此,当
WeakListener
的引用被垃圾回收时,
WeakListener
实现最终会确保从
Observable
中删除
WeakListener
本身。
如果尚不清楚,我们太空模拟游戏的解决方案将是让游戏引擎对所有在线飞船使用强引用。当飞船上线时,使用弱侦听器(例如
WeakInvalidationListener
)向新玩家注册所有其他在线飞船。当玩家离线时,游戏引擎会删除他对玩家的强引用,并使该玩家有资格进行垃圾回收。游戏引擎无需费心将离线玩家作为其他玩家的侦听器明确删除。
情境2
不行。 为更好地理解接下来要说的,请先阅读我的情境1答案。
BooleanPropertyBase
存储了对 otherBool
的强引用。这本身并不会导致 otherBool
总是可达,从而潜在地导致内存泄漏。当 bool
变得不可达时,所有存储的引用也将变得不可达(假设它们没有被存储在其他地方)。
BooleanPropertyBase
还通过将自身作为绑定属性的观察者来工作。然而,它通过包装自己在一个类中来实现,该类几乎与我在第一种情况中描述的 WeakListener
相同。因此,一旦你将 bool
置空,它将很快从 otherBool
中移除。