虚拟参考用于事后分析操作。Java 规范指出 幻象引用对象 在幻像引用本身被清理之前不会被释放。

我的问题是:此功能(未释放对象)的用途是什么?

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

有帮助吗?

解决方案

我能想到的唯一可以防止释放的好用例是某种 JNI 实现的异步数据源正在写入引用的对象,并且必须被告知停止写入 - 停止写入对象 -在内存被回收之前。如果允许事先释放,一个简单的忘记处置()错误可能会导致内存损坏。

这是过去使用 Finalize() 的情况之一,并且可能导致了它的一些怪癖。

其他提示

编辑,因为我首先误解了这个问题:

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

Java规范说,当参考对象被征用时,幻影参考不会清除,但实际上,语言没有办法说明是否已经完成。在某些实现中,JNI弱的全局参考比幻影参考较弱,并提供了一种访问幻影可触及对象的方法。

但我没有发现其他参考文献会说同样的话。

我认为这个想法是让其他对象在原始对象所做的范围之外进行额外的清理。例如,如果原始对象无法扩展以实现某些终结功能,则可以使用幻像引用。

更大的问题是 JVM 不保证对象将被最终确定,并且我认为扩展后也不能保证幻像引用在最终确定后执行其操作。

幻像引用可用于执行预垃圾收集操作,例如释放资源。相反,人们通常使用 Finalize() 方法来实现此目的,这不是一个好主意。终结器会对垃圾收集器的性能产生可怕的影响,并且如果您不非常小心,可能会破坏应用程序的数据完整性,因为“终结器”是在随机线程中随机调用的。

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

有时,您可以轮询 ReferenceQueue 并检查是否有任何新的 PhantomReference,其引用的对象已变为幻像可达。为了能够做任何有用的事情,例如可以从 java.lang.ref.PhantomReference 派生一个类,该类引用应在垃圾收集之前释放的资源。只有当幻像引用本身变得不可访问时,引用的对象才会被垃圾回收。

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

对于没有生命周期管理机制但您正在使用需要显式生命周期管理的东西来实现的 API 来说,这是一个完美的解决方案。

特别是任何类型的 API,过去只使用内存中的对象,但您使用套接字连接或文件连接到其他更大的后备存储来重新实现,可以使用 PhantomReference 在调用之前“关闭”并清理连接信息。对象被 GC 处理,并且连接从未关闭,因为没有可以使用的生命周期管理 API 接口。

考虑将简单的 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;

    /**
     * This class provides a way for tracking the loss of reference of one type of
     * object to allow a secondary reference to be used to perform some cleanup
     * activity.  The most common use of this is with one object which might
     * contain or refer to another object that needs some cleanup performed
     * when the referer is no longer referenced.
     * 

*一个示例可能是类型持有人的对象,该对象是指或使用 *套接字连接。当引用丢失时,插座应 *关闭。因此,可以像 *中一样创建一个实例

     *    ReferenceTracker trker = ReferenceTracker() {
     *        public void released( Socket s ) {
     *            try {
     *                s.close();
     *            } catch( Exception ex ) {
     *                log.log( Level.SEVERE, ex.toString(), ex );
     *            }
     *        }
     *  };
     * 
* Somewhere, there might be calls such as the following. *
     *        interface Holder {
     *            public T get();
     *        }
     *        class SocketHolder implements Holder {
     *            Socket s;
     *            public SocketHolder( Socket sock ) {
     *                s = sock;
     *            }
     *            public Socket get() {
     *                return s;
     *            }
     *        }
     * 
* This defines an implementation of the Holder interface which holds * a reference to Socket objects. The use of the trker * object, above, might then include the use of a method for creating * the objects and registering the references as shown below. *
     *    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;
     *    }
     * 
* Software wishing to use a socket connection, and pass it around would * use SocketHolder.get() to reference the Socket instance, in all cases. * then, when all SocketHolder references are dropped, the socket would * be closed by the released(java.net.Socket) method shown * above. *

* {@link referenceTracker}类使用{@link phantomReference}将第一个参数用作 *保存第二个参数的映射的键。因此,当释放 *密钥实例时,可以将密钥引用排队,可以从 *队列中删除,并用于从映射中删除该值,然后将其传递给 * reparion()。* /公共抽象类ReferenceTracker { / ***正在从参考队列中删除条目的线程实例,如它们所示。*/私人挥发式refqueeepoll民意调查;/***用于此实例的记录器实例。如果使用该构造函数,它将包含名称为后缀 *。*/ private static final logger log = logger.getLogger(referenceTracker.class.getName());/** *名称指示这是用于记录的实例和所需的其他实例的其他分离。*/私人最终字符串;/** *使用传递的名称创建一个新实例的引用术语的新实例,以区分 *在loggging and toString()实现中的实例。* @param which 该实例的名称,用于在日志记录等中区分多个实例。*/ public referencenceTracker(字符串){this.the whth = when;} /***创建一个没有合格名称的ReferenceTracker的新实例。*/ public referencenceTracker(){this.th = null;} /***提供了对此实例名称的访问。* @return 该实例的名称。*/ @override public string tostring(){if(when == null){return super.tostring()+“:参考跟踪器”;}返回super.tostring()+“:ReferenceTracker["+which+"]";} /***子类必须实现此方法。当删除 *相关持有人对象的所有引用时,将调用。* @param val以第二个参数传递给{@link #trackReference(object,object)trackReference(t,k)} */公共抽象void(k val);/ **引用持有人对象的参考队列*/ private final firmon referencequeuerefquequeququeque = new ReferenceQueue();/** *随着条目已被跟踪,已创建然后破坏的线程总数的计数。当跟踪引用为零时,就没有队列在运行。*/私有最终原子tcnt = new atomicInteger();私有易失性布尔运算;/** *一个线程实现{@link #refqueue},然后随后拨打{@link reparese(k)} *,因为删除了对t对象的引用。* / private类RefqueEepoll扩展了线程{ / ***与此实例关联的线程号。可能有两个实例 *这个类别存在于挥发性系统中。如果是这种情况,则在某些日志记录中可见该值以区分活动性值。*/私人最终int mycnt;/***创建此类的实例。*/ public refqueUepoll(){setDaemon(true);setName( getClass().getName()+":ReferenceTracker("+which+")");mycnt = tcnt.incrementAndGet();} /***此方法提供了执行的所有活动 refqueue.remove()* 调用然后调用 released(K) 让应用程序发布所需的 *资源。*/ public @override void run(){try {dorun();} catch(可投掷ex){log.log(完成?级别.INFO :Level.Severe,Ex.ToString()+“:幻像引用轮询线程停止”,ex );}最后{runn = false;}}私人挥发性布尔= false;private void dorun(){while(!完成){参考ref = null;尝试{running = true;ref = refqueue.remove();K控制;同步(refMap){ctl = refmap.remove(ref);完成 = actCnt.decrementAndGet() == 0;if(log.isloggable(level.fine)){log.log(level.fine,“当前ACT REFS refs = {0},mapsize = {1}”,new Object [] {actcnt.get(),refmap.size ()});} if(actcnt.get()!= refmap.size()){throwable ex = new IllegalStateException(“活动引用和映射大小的计数不同步”);log.log(Level.SEVERE, ex.toString(), ex);}} if(log.isloggable(level.finer)){log.log(level.finer,“引用:{0}, dep={1}", new Object[]{ref, ctl});} if(ctl!= null){try {reputs(ctl);if(log.isloggable(level.fine)){log.log(level.fine,“释放的依赖对象:{0}”,ctl);}} catch(runtimeException ex){log.log(level.severe,e​​x.toString(),ex);}}} catch(except ex){log.log(level.severe,e​​x.toString(),ex);}最后{if(ref!= null){ref.Clear();}}} if(log.isloggable(level.fine)){log.log(level.fine,“ poll thread {0} for {1} for {1}”,新对象[] {m​​ycnt,this});}}} /***活动引用的计数。*/私人最终AtomicInteger actcnt = new AtomicInteger();/ ** *从t引用到k对象的映射,用于发布(k)呼叫 */ private final concurrenthashmap,k> refmap = new concurrenthashmap,k>();/***添加了一个跟踪的参考。DEP不应以任何方式参考Ref,除非可能 *弱Repreference。dep 几乎总是由 ref 引用的东西。* @throws IllegalArgumentException ref 和 dep 是同一个对象。* @param dep 当 ref 不再被引用时需要清理的依赖对象。* @param ref将要跟踪的引用的对象 */ public void trackReference(t ref,k dep){if(ref == dep){抛出新的illegalargumentException(“引用的对象和依赖对象都不能相同”) ;}幻象reReference p =新的phantomReference(参考,refqueue);同步(refMap){refmap.put(p,dep);if(actcnt.getAndIncrement()== 0 || running == false){if(actcnt.get()> 0 && runn == false){if(log.isloggable(level.fine)){log.fine(log.fine(log.fine)( “开始停止幻影涉及轮询线”);}} poll = new RefqueUepoll();轮询.start();if(log.isloggable(level.fine)){log.log(level.fine,“ poll thread#{0}为{1}创建的{1}”,new Object [] {tcnt.get(),this});}}}}} /** *如果跟踪器所在的jvm,正在停机,或者某些其他上下文正在关闭,并且现在应发布跟踪器跟踪 *的对象 *,则可以调用此方法。此方法将导致 * {@link #realeard(object)warree(k)}被列为每个未偿还的参考。*/ public void shutdown(){listrem;//复制值并清除地图,以便释放一次//仅被调用一次,incase gc后来驱逐引用同步(refMap){rem = new arraylist(refmap.values());refmap.clear();} for(k dep:rem){尝试{prokeard(dep);} catch(exception ex){log.log(level.severe,e​​x.toString(),ex);}}}}}

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

您也可以使用框架(即工厂),其中引用作为虚拟引用给出。如果对象很多且寿命较短(即,使用然后处置)。如果你有草率的程序员认为垃圾收集很神奇,那么清理内存非常方便。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top