8

幻影参考用于事后操作。Java 规范指出,在清除幻像引用本身之前,不会释放幻像引用的对象。

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

(我想出的唯一想法是允许本机代码对对象进行事后清理,但这并没有多大说服力)。

4

6 回答 6

2

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

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

Java 规范说,当引用对象入队时,幻像引用不会被清除,但实际上,语言中没有办法判断这是否已经完成。在某些实现中,JNI 弱全局引用比幻像引用更弱,并提供了一种访问幻像可达对象的方法。

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

于 2008-09-17T07:38:38.630 回答
1

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

更大的问题是 JVM 不能保证一个对象会被最终确定,而且我假设不能保证幻像引用在最终确定后做他们的事情。

于 2008-09-17T07:42:39.683 回答
1
幻影引用可用于执行垃圾回收前的操作,例如释放资源。相反,人们通常为此使用 finalize() 方法,这不是一个好主意。终结器对垃圾收集器的性能有可怕的影响,如果你不是很小心,它可能会破坏应用程序的数据完整性,因为“终结器”是在随机线程中随机调用的。

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

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

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

于 2008-09-17T14:52:44.127 回答
1

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

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

于 2011-10-13T13:41:30.717 回答
0

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

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

考虑将一个简单的 Map 地图移动到数据库中。当地图引用被丢弃时,没有明确的“关闭”操作。然而,如果您已经实现了通过缓存写入,您希望能够完成任何写入并关闭与“数据库”的套接字连接。

下面是我用于这类东西的一个类。请注意,对 PhantomReferences 的引用必须是非本地引用才能正常工作。否则,jit 将导致它们在您退出代码块之前过早排队。

    导入 java.lang.ref.PhantomReference;
    导入 java.lang.ref.Reference;
    导入 java.lang.ref.ReferenceQueue;
    导入 java.util.ArrayList;
    导入 java.util.List;
    导入 java.util.concurrent.ConcurrentHashMap;
    导入 java.util.concurrent.atomic.AtomicInteger;
    导入 java.util.logging.Level;
    导入 java.util.logging.Logger;

    /**
     * 该类提供了一种跟踪一种类型的参考丢失的方法
     * 对象以允许使用辅助引用来执行一些清理
     * 活动。最常见的用法是用于一个对象,它可能
     * 包含或引用另一个需要执行一些清理的对象
     * 当不再引用引用者时。
     *

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

     * ReferenceTracker trker = ReferenceTracker() {
     * public void 已发布(Socket s){
     * 尝试 {
     * s.close();
     * } 捕捉(异常前){
     * log.log(Level.SEVERE, ex.toString(), ex);
     * }
     * }
     * };
     *
* 在某个地方,可能会有如下调用。 *
     * 接口持有人{
     * public T get();
     * }
     * 类 SocketHolder 实现 Holder {
     * 套接字;
     * 公共 SocketHolder( 套接字 sock ) {
     * s = 袜子;
     * }
     * 公共套接字 get() {
     * 返回 s;
     * }
     * }
     *
* 这定义了持有者接口的实现,它持有 * 对 Socket 对象的引用。的使用trker * 上面的对象可能包括使用创建方法的方法 * 对象和注册引用如下所示。 *
     * public SocketHolder connect(String host, int port) 抛出 IOException {
     * 套接字 s = 新套接字(主机,端口);
     * SocketHolder h = 新的 SocketHolder(s);
     * trker.trackReference(h, s);
     * 返回 h;
     * }
     *
* 希望使用套接字连接并传递它的软件 * 在所有情况下,使用 SocketHolder.get() 来引用 Socket 实例。 * 然后,当所有 SocketHolder 引用都被删除时,套接字将released(java.net.Socket)* 按所示方法 关闭 * 多于。 *

* {@link ReferenceTracker} 类对第一个参数使用 {@link PhantomReference} 作为 * 持有对第二个参数的引用的映射的键。因此,当 * key instance被释放,key引用入队,可以从中移除 * 队列,用于从映射中删除值,然后传递给 * 释放()。 */ 公共抽象类 ReferenceTracker { /** * 正在从引用队列 refqueue 中删除条目的线程实例。 */ 私有易失性 RefQueuePoll 轮询; /** * 用于此实例的 Logger 实例。它将包含名称作为后缀 * 如果使用了该构造函数。 */ private static final Logger log = Logger.getLogger(ReferenceTracker.class.getName()); /** * 名称表明这是用于记录和其他分离的实例 * 需要实例。 */ 私有最终字符串其中; /** * 使用传递的名称创建一个新的 ReferenceTracker 实例以区分 * logging 和 toString() 实现中的实例。 * @param which 该实例的名称用于区分日志记录等中的多个实例。 */ 公共参考跟踪器(字符串){ 这.哪个=哪个; } /** * 创建一个没有限定名称的 ReferenceTracker 新实例。 */ 公共参考跟踪器(){ this.which = null; } /** * 提供对该实例名称的访问。 * @return 此实例的名称。 */ @覆盖 公共字符串 toString() { 如果(其中==空){ return super.toString()+": ReferenceTracker"; } return super.toString()+": ReferenceTracker["+which+"]"; } /** * 子类必须实现此方法。当所有对 * 关联的持有者对象被删除。 * @param val 作为第二个参数传递给相应调用 {@link #trackReference(Object, Object) trackReference(T,K)} 的值 */ 公共抽象无效释放(K val); /** 对持有者对象的引用的引用队列 */ 私有最终 ReferenceQueuerefqueue = new ReferenceQueue(); /** * 已创建并销毁为条目的线程总数 * 被跟踪。当跟踪引用为零时,没有队列运行。 */ 私有最终 AtomicInteger tcnt = new AtomicInteger(); 私有 volatile 布尔运行; /** * 一个线程实现,它轮询 {@link #refqueue} 以随后调用 {@link released(K)} * 作为对 T 对象的引用被删除。 */ 私有类 RefQueuePoll 扩展线程 { /** * 与此实例关联的线程号。可能有两种情况 * 此类存在于易失性系统中。如果是这种情况,该值将 * 在某些日志中可见以区分活动日志。 */ 私人最终 int mycnt; /** * 创建这个类的一个实例。 */ 公共 RefQueuePoll() { 设置守护进程(真); setName(getClass().getName()+": ReferenceTracker("+which+")"); mycnt = tcnt.incrementAndGet(); } /** * 此方法提供执行refqueue.remove() * 调用然后调用released(K)以让应用程序释放的所有活动 * 所需资源。 */ 公共@Override 无效运行(){ 尝试 { 运行(); } 捕捉(可投掷的前) { log.log(完成?Level.INFO:Level.SEVERE, ex.toString()+": phantom ref poll 线程停止", ex); } 最后 { 运行=假; } } 私有易失性布尔完成=假; 私人无效doRun(){ 而(!完成){ 参考 ref = null; 尝试 { 运行=真; ref = refqueue.remove(); ķctl; 同步(参考地图){ ctl = refmap.remove(ref); 完成 = actCnt.decrementAndGet() == 0; if(log.isLoggable(Level.FINE)){ log.log(Level.FINE, "当前行为 refs={0}, mapsize={1}", new Object[]{actCnt.get(), refmap.size()}); } 如果(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}); } 如果(ctl!= null){ 尝试 { 释放(ctl); if(log.isLoggable(Level.FINE)){ log.log(Level.FINE, "发布的依赖对象:{0}", ctl); } } 捕捉(运行时异常前){ log.log(Level.SEVERE, ex.toString(), ex); } } } 捕捉(异常前) { log.log(Level.SEVERE, ex.toString(), ex); } 最后 { 如果(参考!=空){ ref.clear(); } } } if(log.isLoggable(Level.FINE)){ log.log(Level.FINE, "poll thread {0} shutdown for {1}", new Object[]{mycnt, this}); } } } /** * 活动引用的计数。 */ 私有最终 AtomicInteger actCnt = new AtomicInteger(); /** * 从 T 引用映射到要用于释放(K)调用的 K 对象 */ private final ConcurrentHashMap,K>refmap = new ConcurrentHashMap,K>(); /** * 添加跟踪参考。dep 不应以任何方式引用 ref,除非可能 * 弱引用。dep 几乎总是 ref 所指的东西。 * @throws IllegalArgumentException of ref 和 dep 是同一个对象。 * @param dep 不再引用 ref 时需要清理的依赖对象。 * @param ref 要跟踪其引用的对象 */ 公共无效trackReference(T ref,K dep){ 如果(参考== dep){ throw new IllegalArgumentException("引用对象和依赖对象不能相同"); } PhantomReference p = new PhantomReference(ref, refqueue); 同步(参考地图){ refmap.put(p,dep); if(actCnt.getAndIncrement() == 0 || 运行 == false) { if(actCnt.get() > 0 && running == false) { if (log.isLoggable(Level.FINE)) { log.fine("启动停止的幻像 ref 轮询线程"); } } 轮询 = 新的 RefQueuePoll(); poll.start(); if(log.isLoggable(Level.FINE)){ log.log(Level.FINE, "为 {1} 创建的轮询线程 #{0}", new Object[]{tcnt.get(), this}); } } } } /** * 如果跟踪器所在的 JVM 正在被调用,则可以调用此方法 * 关闭,或其他上下文正在关闭并跟踪对象 * 现在应该由跟踪器发布。这种方法会导致 * {@link #released(Object) deleted(K) } 为每个未完成的引用调用。 */ 公共无效关机(){ 利斯特伦; // 复制值并清除地图以便释放 // 只被调用一次,以防 GC 稍后驱逐引用 同步(参考地图){ rem = new ArrayList(refmap.values()); refmap.clear(); } 为(K dep:rem){ 尝试 { 发布(dep); } 捕捉(异常前) { log.log(Level.SEVERE, ex.toString(), ex); } } } }

于 2017-02-24T15:54:36.907 回答
-2

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

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

于 2008-09-17T07:40:59.920 回答