幽灵引用对象

8

幽灵引用用于死后操作。

Java规范指出,幽灵引用对象将不会被释放,直到幽灵引用本身被清除。

我的问题是:这个特性(对象不被释放)有什么作用?

(我唯一想到的想法是允许本地代码对对象进行死后清理,但这并不太令人信服。)

6个回答

2

编辑,因为我之前误解了问题:

引用自这里http://www.memorymanagement.org/glossary/p.html:

Java规范说明虚引用在引用对象入队时不会被清除,但实际上,在语言中无法确定是否已经执行了该操作。在某些实现中,JNI弱全局引用比虚引用更弱,并提供了一种访问虚可达对象的方法。

但我没有找到其他说同样话的参考资料。


我的问题不是关于PhantomReferences的有用性,而是为什么在清除幽灵引用之前不将对象释放。 - Shimi Bandiel
啊,好的,我误将PhantomReferences视为“这个特性”了。 - jrudolph
是的,我知道,但你是否熟悉一些可以利用这个的用例呢? - Shimi Bandiel

1

我认为这个想法是让其他对象在原始对象之上进行额外的清理工作。例如,如果原始对象无法扩展以实现一些终结工作,您可以使用虚引用。

更大的问题是JVM不能保证对象会被终结,我假设在此基础上也不能保证虚引用在终结后执行它们的任务。


我的问题是,为什么对象在最终化后仍会存在于内存中?你无法从Java代码中访问它们,那么为什么要等到PhantomReference本身被清除呢? - Shimi Bandiel
1
@ShimiBandiel 考虑一个引用其他对象 A 和 B 的对象 O。对象 O 可能被 finalised,此时你可能希望在 A 和 B 被 finalised 之前运行一些清理操作。通过将其保持在 phantom 状态,JVM 允许您在清理 O 期间访问 A、B。 - HRJ

1
幽灵引用可用于执行预垃圾收集操作,例如释放资源。但人们通常使用finalize()方法来实现这一点,这不是一个好主意。Finalizer对垃圾收集器的性能有着可怕的影响,并且如果您不非常小心,它可能会破坏应用程序的数据完整性,因为“finalizer”在随机线程中,在随机时间被调用。

在幽灵引用的构造函数中,您可以指定一个ReferenceQueue,一旦引用对象变为“幽灵可达”,则将幽灵引用排队。幽灵可达意味着除了通过幽灵引用之外无法访问。最初令人困惑的是,尽管幽灵引用继续在私有字段中持有引用对象(与软引用或弱引用不同),但其getReference()方法始终返回null。这样做是为了防止您再次使对象强可达。

不时地,您可以轮询ReferenceQueue并检查是否有任何新的PhantomReferences,其引用对象已成为幽灵可达。为了能够执行任何有用的操作,例如可以从java.lang.ref.PhantomReference派生一个类,该类引用应在垃圾收集之前释放的资源。只有当幽灵引用本身变得不可达时,引用对象才会被垃圾收集。

http://www.javalobby.org/java/forums/m91822870.html#91822413


但是为什么在调用finalize()之后它没有被释放?为什么要等待SoftReference被清理?这满足了哪些使用案例? - Shimi Bandiel

1
我能想到的唯一一个好的使用情况,可以防止解除分配的是一种JNI实现的异步数据源正在写入引用对象,并且必须告诉它停下来,在内存回收之前停止写入对象。 如果允许先前的解除分配,则简单的忘记调用dispose() bug可能会导致内存损坏。
这是过去finalize()将被使用的情况之一,也可能驱动了一些它的怪癖。

0

这是一个完美的解决方案,适用于没有生命周期管理机制但需要使用显式生命周期管理的API。

特别是任何使用内存中对象的API,但是您重新实现了使用套接字连接或文件连接到其他较大后备存储的API,可以使用PhantomReference在对象被GC并且未关闭连接因为没有生命周期管理API接口的情况下,“关闭”和清理连接信息。

想象一下将简单的Map映射到数据库中。当Map引用被丢弃时,没有明确的“关闭”操作。但是,如果您已实施写入缓存,您希望能够完成任何写入并关闭到您的“数据库”的套接字连接。

以下是我用于此类操作的类。请注意,对于PhantomReferences的引用必须是非局部引用才能正确工作。否则,即使您退出代码块,jit也会导致它们过早排队。

    import java.lang.ref.PhantomReference;
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.logging.Level;
    import java.util.logging.Logger;
/** * 此类提供了一种跟踪某种对象引用丢失的方法,以允许使用次要引用执行一些清理活动。最常见的用法是一个对象可能包含或引用另一个需要在引用者不再被引用时执行一些清理操作的对象。 *

* 例如,可能会有一个类型为Holder的对象,它引用或使用Socket连接。当引用丢失时,应关闭套接字。因此,可以创建一个实例,如下所示: *

     *    ReferenceTracker trker = ReferenceTracker() {
     *        public void released( Socket s ) {
     *            try {
     *                s.close();
     *            } catch( Exception ex ) {
     *                log.log( Level.SEVERE, ex.toString(), ex );
     *            }
     *        }
     *  };
     * 
* 然后,在某个地方,可能会调用以下方法。 *
     *        interface Holder {
     *            public T get();
     *        }
     *        class SocketHolder implements Holder {
     *            Socket s;
     *            public SocketHolder( Socket sock ) {
     *                s = sock;
     *            }
     *            public Socket get() {
     *                return s;
     *            }
     *        }
     * 
* 这定义了一个Holder接口的实现,它持有对Socket对象的引用。上面的trker对象的使用可能还包括使用以下方法来创建对象并注册引用。 *
     *    public SocketHolder connect( String host, int port ) throws IOException {
     *        Socket s = new Socket( host, port );
     *        SocketHolder h = new SocketHolder( s );
     *        trker.trackReference( h, s );
     *        return h;
     *    }
     * 
* 希望使用套接字连接并传递它的软件将使用SocketHolder.get()在所有情况下引用Socket实例。然后,当所有SocketHolder引用都被删除时,套接字将通过上面显示的released(java.net.Socket)方法关闭。 *

* {@link ReferenceTracker}类使用{@link PhantomReference}作为第一个参数的键,以保留对第二个参数的引用。因此,当释放关键实例时,可以将关键引用排队,可以从队列中删除,并用于从映射中删除值,然后将其传递给released()。 */ public abstract class ReferenceTracker { /** * 从引用队列refqueue中移除条目的线程实例。 */ private volatile RefQueuePoll poll; /** * 此实例用于此实例的Logger。如果使用该构造函数,则它将包含名称作为后缀。 */ private static final Logger log = Logger.getLogger(ReferenceTracker.class.getName()); /** * 表示此实例的名称,以进行日志记录和其他所需的实例分离。 */ private final String which;

/** * 使用传递的名称创建ReferenceTracker的新实例,以区分日志记录和toString()实现中的实例。 * @param which 用于在日志记录等中区分多个实例的此实例的名称。 */ public ReferenceTracker( String which ) { this.which = which; }
/** * 创建没有限定名称的ReferenceTracker的新实例。 */ public ReferenceTracker( ) { this.which = null; }
/** * 提供对此实例名称的访问。 * @return 此实例的名称。 */ @Override public String toString() { if( which == null ) { return super.toString()+": ReferenceTracker";

-2

它可以让您拥有幻影缓存,这在内存管理方面非常高效。 简单来说,如果您有巨大的对象,创建成本很高但很少使用,您可以使用幻影缓存引用它们,并确保它们不占用更有价值的内存。如果您使用常规引用,则必须手动确保没有对该对象的引用。您可以对任何对象提出同样的论点,但是您不必手动管理幻影缓存中的引用。只需小心检查它们是否已被收集即可。

此外,您可以使用框架(即工厂),其中引用被视为幻影引用。如果对象很多且寿命短暂(即使用后即处置),则这非常有用。如果您有粗心的程序员认为垃圾回收是神奇的,则非常方便清除内存。


我认为你混淆了SoftRefrences、WeakReferences和PhantomReferences之间的区别。 - Shimi Bandiel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接