0

我在研究不同类型的参考资料时遇到了这个词(复活)。对我来说令人困惑的领域之一是幻影参考。到现在为止,我从来没有在现实中遇到过一个用例,事后我觉得我应该使用 Phantom Reference。

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

为了清楚起见,我了解 finalize 和 phantom reference 中 Object “resurrection” 的定义

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

  1. 何时使用对象复活?
  2. 何时使用幻影参考?
  3. 幻影引用如何解决无意中的对象复活

我非常感谢围绕这个主题进行讨论。这些是几个对我来说仍然模糊的领域

谢谢, 阿比吉特

4

1 回答 1

0

Java 有两种不同的机制来响应对象的释放。旧的机制 usingfinalize会在对象被释放之前运行一个特定的方法。新机制 using允许您在对象被释放PhantomReference立即运行特定方法。1

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

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

那么为什么要使用幻像参考呢?基本上,任何你想对对象的释放做出反应的情况都应该使用PhantomReference,因为它使用的 API 可以防止终结器的各种常见错误。finalize(现在已弃用)应仅用于确实没有其他选择的情况。

不幸的是PhantomReference,尽管 API 比 for 更难误用,但finalize通常也更难使用:

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

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


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

2复活的对象保持分配状态,直到它变得无法访问,此时它在不运行其终结器的情况下被释放,除非它被同时释放的其他对象的终结器第二次复活。依赖此行为的代码可能已损坏。

于 2019-03-26T18:57:44.657 回答