什么是垃圾收集中的复活?幻影参考如何解决它?它有什么实际的例子吗?

时间:2013-03-25 06:25:14

标签: java garbage-collection

在研究不同类型的参考文献时,我遇到过这个术语(复活)。对我来说,一个令人困惑的领域是幻影参考。直到现在我还没有遇到过现实中的一个用例,事后我觉得我应该使用Phantom Reference。

在搜索用例时,我发现Phantom Reference可以防止对象复活。

为了说清楚,我理解了对象"复活"的定义。在最终确定和幻像参考

我遇到麻烦的地方是找到一个真实的"用例

  1. 何时使用对象复活?
  2. 何时使用幻影参考?
  3. 幻影参考如何解决无意中的物体复活
  4. 我非常感谢围绕这个主题的讨论。这几个区域对我来说仍然很模糊

    谢谢, 作者Abhijit

1 个答案:

答案 0 :(得分:0)

Java有两种不同的机制来对对象的释放进行响应。较旧的机制使用finalize,在释放对象之前运行特定的方法。相反,使用PhantomReference的新机制允许您在释放对象之后 运行特定方法。 1

finalize技术更强大,因为您可以在对象的this释放时访问它;但是也更加危险,因为有可能(有意或无意)在终结器中创建对该对象的新引用。这可以直接完成(例如,通过将this分配给静态字段),也可以间接完成(例如,两个对象同时变为未引用,一个引用另一个对象,因此最终确定的对象最终通过a间接访问)。不同对象的终结器的字段)。这种情况下,一个对象最终完成,但是仍然可以从某个地方到达,这种情况称为对象复活。尽管它已经定义了语义 2 ,但在实践中它们往往是相当成问题的语义,通常被视为等同于未定义的行为。

对对象释放进行反应的PhantomReference方法基本上是一种约束形式的终结处理,它通过不提供给您这样做的工具来防止您犯任何错误:对象已经(有效)被释放了在您对解除分配作出反应之前,因此您没有机会意外地将其或同时解除的其他任何对象复活。 (特别是PhantomReference不能访问对象的this指针; PhantomReference#get总是返回null。)幻象引用还具有其他优点,例如。该API使您可以精确控制终结器在哪个线程上运行以及当时将执行什么操作。

那么您为什么要使用幻像引用?基本上,任何您想对对象的释放进行反应的情况,都应尽可能使用PhantomReference,因为它使用的API可以防止终结器发生各种常见错误。 finalize(现已弃用)仅应在确实没有其他选择的情况下保留。

不幸的是,尽管PhantomReference的API比finalize的API更难滥用,但在一般情况下也更难使用:

  • 您需要一个对象来容纳PhantomReference本身; PhantomReference仅在该对象仍然存在时才会触发。例如,如果您想在某个对象死亡时从地图上删除有关该对象的元数据,则将PhantomReference作为/在实现该地图的同一对象中的单独字段中存储是有意义的(这就是方法WeakMap实际上在Java中有效。如果您要使用终结器来管理文件句柄等全局资源,则PhantomReference将需要通过某种全局结构(例如某个类的静态字段中的集合)保持有效。
  • 您需要一个ReferenceQueue来处理终结器的调度。
  • 您需要一种可以完成终结工作的方法-当您释放所监视的对象时,该方法将运行。 PhantomReference并没有直接提供其中之一;通常的技术是扩展PhantomReference并为生成的派生类提供有问题的方法。
  • 您需要轮询参考队列;该操作指定终结器在哪个线程上运行以及当时在做什么。可能的情况包括使用单独的线程进行参考队列轮询,或者在程序没有做任何重要的事情时(例如,在开始阻塞输入之前)使用程序的主线程。
  • 对参考队列进行轮询实际上并没有运行终结器(毕竟,PhantomReference没有直接为终结器上运行的方法提供服务)。而是,轮询参考队列只会给您PhantomReference对象带来了释放。由于PhantomReference类本身没有有用的方法来对此作出反应,因此您需要将其强制转换为适当的类,然后运行您创建的方法。
  • 对引用队列进行轮询也不会取消分配PhantomReference对象(您必须将其保持在其他对象中;否则它将无法工作)。因此,当您看到幻影引用出队时,如果要避免内存泄漏,则必须手动将其从保持其生存状态的任何对象(通常是集合)中删除。
  • 如果您需要有关已释放对象的更多信息(例如,在WeakMap的情况下,这是对需要删除的地图条目的引用),则必须将其存储在某个地方,因为在释放对象时,否则它将不可用。通常,您会将数据存储在PhantomReference本身中(因为无论如何您都在使用它的派生类,因此可以在派生类中创建字段来存储数据)。请记住,这不能引用您要对解除分配作出反应的对象,因为否则,您最终将使该对象保持活动状态,并且永远不会解除分配。

尽管您可以自己处理所有这些复杂性,并且有时偶尔需要,如果您想对幻影引用做一些不寻常的事情,那么使用预编写的库将所需的操作包装到一个更方便的API。例如,java.lang.ref.Cleaner在内部使用幻像引用来提供与finalize相似的API,但是(因为它基于幻像引用)可以安全地防止意外复活和类似问题。这样,虽然幻像引用通常对于响应无法访问的对象通常非常有用,但是程序员实际上很少直接处理它们。使用内部使用它们的库会更常见。


1 在Java的早期版本中,幻象引用技术实际上为分配的对象保留内存,直到清除了幻象引用为止。但这只是一个实现细节,因为无法访问有问题的内存,并且应该将对象视为已从幻像引用处理程序中释放,因为您无论如何都无法对其进行任何处理。

2 复活的对象将保持分配状态,直到它变得不可访问为止,此时将其重新分配而无需运行其终结器,除非该对象被终结器再次第二次复活,而该对象在同时。依赖此行为的代码可能已损坏。