手动加入队列的弱引用状态是什么?

10

当您手动将一个引用加入队列时,对象的状态是什么?

this.s = "foo";
WeakReference<String> wr = new WeakReference<String>(this.s);
wr.enqueue();
我找到的所有文档都谈论垃圾回收器如何将对象放入队列,而不是手动执行时会发生什么。
这是否有任何情况是有意义的?什么意思是一个对象被加入队列,但仍然具有可到达的引用(强引用、弱引用、虚引用)?
编辑:额外的奖励跟进问题:当对象在遥远的未来变得不可访问时,它是否会再次被加入队列?

你认为手动将引用加入队列可能带来什么好处呢?引用队列的存在是为了通知您发生了某些事情,而不是触发行为。 - kdgregory
@kdgregory - 这正是我要问的问题。为什么要这样做,如果你这样做了,它意味着什么?有一种手动方法可以实现,因此它可能并不完全无用。 - James Moore
JDK足够大,而且历史悠久,有很多位没有合理解释的位。也许当时有人认为这是个好主意。也许某个早期版本的JDK需要它,然后为了向后兼容性而无法删除。虽然我赞赏你学习的愿望,但我认为为了学习而学习是相当无意义的。 - kdgregory
7
@kdgregory,我认为你在这里是错误的。了解事物如何工作几乎总是有用的(而且无论如何为自己学习都是一件好事)。JDK并不是很古老,许多在1996年参与其开发的人仍然活跃在职业领域,并且有大量关于其开发过程的信息可供查阅。 - James Moore
4个回答

2

有一篇关于Java引用类型的好文章: http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html

简单来说,你可以这样做:

public class Main {
    public static void main(String[] args) throws InterruptedException{
        String weakString = "Help me";
        ReferenceQueue<String> refQ = new ReferenceQueue<String>();
        WeakReference<String> weakRef = new WeakReference<String>(weakString, refQ);
        //there is no more "hard reference to the string"
        weakString = null;
        //there is no object in ReferenceQueue
        System.out.println(refQ.poll());
        //put the object in the ReferenceQueue
        weakRef.enqueue();
        while (true) {
            //There will be returned "Help me" until the garbage collector will "kill" 
            //"weak" string. This will be done after some time because there are no                                     
            //more strong references to the string. After that will be returned a null  
            //object.
            System.out.println(weakRef.get());
            //only one time there will be returned a refenrece to the String object
            System.out.println(refQ.poll());
            Thread.sleep(1000);
        }
    }
}

这是为了与某种缓存一起使用而设计的。通过这种方式,您可以确保内存中不会有长时间未使用的对象。 ReferenceQueue可用于在调用weakRef.enqueue();后执行某些清理操作。例如,关闭用于处理数据的文件或流。


我看到了那篇文章,但它似乎有些错误:“这种引用的唯一用途是跟踪它何时被加入到ReferenceQueue中,因为在那个时候,您就知道它所指向的对象已经死亡。”如果您可以在仍然具有可达引用的情况下手动将某些内容加入队列中,则不能仅仅因为已将其加入队列中就假定它已经死亡。 - James Moore
在我看来,ReferenceQueue 提供了手动清除与收集对象相关的一些数据的可能性。因此,如果您有一个定期检查队列并清除对象的方法,"enqueue" 方法为您提供了在 GC 收集对象之前清除对象的可能性。 - StKiller
但是你可以直接清除它 - 没有必要进行排队。 - James Moore
我的意思是 - 还有另一个线程负责清除弱对象。在应用程序的另一个线程/部分中,您可以将无用的对象加入队列,以此向“清理器”线程发出信号来完成它的工作。 - StKiller
@StKiller 你的例子不起作用。文字字符串永远不会被垃圾收集。请按以下方式分配字符串:String weakString = new String("Help me!"); - Matej 'Yin' Gagyi
@StKiller,你的示例仍然无法按照评论中描述的那样工作。一旦弱引用被排队,GC就永远不会回收它。请参见:https://github.com/yin/java-crafts/blob/master/com/github/yin/java/crafts/memory/WeakReferenceGCExample.java - Matej 'Yin' Gagyi

2
强引用将保留,因此该对象不符合收集条件。正如JVestry所指出的那样,如果您使用队列创建了弱引用,则wr.enqueue()会按预期返回true。
这种方法的一个潜在用途是让您的处理程序能够操作一个对象,该对象可能处于挂起的垃圾回收状态或其他系统状态更改状态,您希望采取与对象被收集时相同的操作,例如,您可能会保留对某个占用系统资源的东西的软引用,以便在您仍然能够管理自己正常完成时,系统可以处理低内存情况。
关于第二个问题,无论是使用enqueue()还是gc作为收集的一部分来执行,引用只会被入队一次。
编辑:需要记住的是,引用队列与引用之间的关系是针对引用(在本例中为wr),而不是引用对象(在本例中为s)。您可以对同一对象拥有多个弱引用并实现多次入队(某种程度上),每个弱引用对应一个入队。但是,队列中保存的是弱引用,而不是引用对象,尽管引用对象仍应该可以通过弱引用访问。有关更多详细信息,请参见java.lang.ref的通知部分中的javadoc。

我并不是要过于苛求这个问题,但你永远不知道代码的其他部分中有多少对弱引用对象的引用,特别是如果它是一个资源。如果你可以丢弃这样的对象,你应该有一个方法来标记它,并执行资源释放。为什么?因为强制其他引用检查已排队的弱引用是OO设计的明显违规。对象的状态不再存在于对象本身中。这也是疯狂运行时错误的敞开大门... - Jérôme Verstrynge
@JVerstry - 如果我正在使用软引用来管理对资源的访问,那么是的,我需要检查reference.get()的结果,因为所引用的对象可能已经被回收。这并不违反任何设计原则。如果资源正在共享,则无论使用软引用还是enqueue来关闭资源,所有对资源的访问都不能安全地假设状态条件,并且所有访问都必须具有一些基本的检查和错误处理。使用enqueue来关闭资源只是重用了我必须放置在处理所引用对象的收集的机制。 - philwb
“在队列中的是弱引用,而不是引用本身。” 这应该对我来说是显而易见的,但出于某种原因却不是。 - James Moore

1
在您的情况下,wr.enqueue() 将返回 false,因为创建 WeakReference 时您没有指定队列。如果您在创建 WeakReference 时提供 ReferenceQueue,则 wr.enqueue() 将返回 true。
手动将弱引用加入队列是没有意义的。这就像写下弱引用已被垃圾回收一样,但实际上并没有:'s' 仍然是 "foo" 的强引用。

enqueue()方法有什么用?如果它永远没有意义,为什么还会存在?(文档非常明确,垃圾收集器本身不使用enqueue(),所以不是这个原因。) - James Moore
这很可能是为了编译和字节码生成而需要的。你需要掌握字节码才能调用相应的enqueue()字节码,但Java不会调用该方法,因为它可以被覆盖。如果它这样做了,用户代码可能会造成很大的混乱并破坏垃圾回收... - Jérôme Verstrynge

-1
这种情况有什么意义吗?
ReferenceQueues有时用于资源管理,在这种情况下,在GC运行之前调用enqueue()来释放资源是有意义的,因为某些资源相当有限(可以在dispose()方法中完成,其中弱引用本身作为安全措施)。
补充:如果清理需要时间,则此方法很有用,因为引用队列可以在其自己的线程中读取,它不会挂起您的主线程。
对象被排队,但仍具有可达引用(强引用、弱引用、虚引用)是什么意思?
文档说明在对象引用设置为null后调用它,因此enqueue()不能直接影响对象状态。当前文档说明了enqueue调用不被GC使用,它的目的是在GC之前将引用排队。引用的对象不会受到此调用的影响,但读取referencequeue的清理代码可能会处理该对象使用的资源。
编辑:现在检查了1.6文档,因此更正了关于gc使用enqueue的错误(旧文档没有指出这一点,但限制gc对用户代码的曝光是有意义的)。

“在GC运行之前自己调用enqueue()来释放资源并不明智”,这并不是完全正确的。仅仅通过调用enqueue()方法,并不能保证资源一定会被释放。这只会欺骗你自己。此外,其他对同一对象的弱引用可能并没有被加入到队列中。 - Jérôme Verstrynge
@JVerstry,这自然取决于处理队列的清理代码。我的意思是当引用具有引用队列执行资源处理时,它可以很有用<b>时</b>,在这种情况下,将一个引用排队就足以供清理代码读取队列。 - josefx
在这种情况下,最好立即清理而不是将清理委托给监视队列的某个东西。 - Jérôme Verstrynge
@JVerstry 参考队列可以在自己的线程上读取,当清理涉及到线程敏感操作时,“立即清理”可能会有问题。调用enqueue是手动处理资源的最简单方法。 - josefx

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