【问题标题】:Why should I use Weak/SoftReference with ReferenceQueue argument?Why cannot I poll original reference and check if null?为什么我应该将 Weak/SoftReference 与 ReferenceQueue 参数一起使用?为什么我不能轮询原始引用并检查是否为空?
【发布时间】:2017-05-14 18:27:15
【问题描述】:

据我了解,检测对象何时被收集并且内存已经为弱/软引用释放的工作用例会轮询此队列,当引用出现在队列中时,我们可以确定内存是空闲的。

WeakReference ref = new WeakReference (new Object()) 

为什么我不能轮询ref 并检查它是否变为空?

附言

根据评论中提供的链接:

如果垃圾收集器发现一个弱可达的对象, 发生以下情况: 1.设置WeakReference对象的引用字段 为 null,从而使其不再引用堆对象。
2.被WeakReference引用的堆对象是 宣布可终结。 3.堆对象的finalize()方法运行时 并且它的内存被释放,WeakReference 对象被添加到它的 ReferenceQueue(如果存在)。

因此,如果本文写的是事实,并且这些步骤有序的弱引用在步骤后变为空,但对象仅在第 3 步时才添加到队列中。

这是真的吗?

是什么原因?

让我们研究代码:

工作规范示例:

public class TestPhantomRefQueue {

    public static void main(String[] args)
            throws InterruptedException {

        Object obj = new Object();
        final ReferenceQueue queue = new ReferenceQueue();

        final WeakReference pRef =
                new WeakReference(obj, queue);

        obj = null;

        new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("Awaiting for GC");

                    // This will block till it is GCd
                    Reference prefFromQueue;
                    while (true) {
                        prefFromQueue = queue.remove();
                        if (prefFromQueue != null) {
                            break;
                        }
                    }                
                    System.out.println("Referenced GC'd");
                    System.out.println(pRef.get());


                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // Wait for 2nd thread to start
        Thread.sleep(2000);

        System.out.println("Invoking GC");
        System.gc();
    }
}

这段代码输出:

Awaiting for GC
Invoking GC
Referenced GC'd
null

好的,我明白它为什么起作用了。

让我们稍微修改一下代码:

public class TestPhantomRefQueue {

    public static void main(String[] args)
            throws InterruptedException {

        Object obj = new Object();
        final ReferenceQueue queue = new ReferenceQueue();

        final WeakReference pRef =
                new WeakReference(obj, queue);

        obj = null;

        new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("Awaiting for GC");

                    while (true) {
                        if (pRef.get() == null) {
                           Thread.sleep(100);
                            break;
                        }
                    }
                    System.out.println("Referenced GC'd");
                    System.out.println(pRef.get());


                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // Wait for 2nd thread to start
        Thread.sleep(2000);

        System.out.println("Invoking GC");
        System.gc();
    }
}

这个变体挂在while循环中并输出:

Awaiting for GC
Invoking GC

请解释这种行为。

【问题讨论】:

  • @Margaret Bloom 看起来不重复
  • 那我的错!你能把问题说得更具体一点吗?也许举个例子?
  • 可以this link 帮忙吗? "因此,当 WeakReference 或 SoftReference 类的 get() 方法返回 null 时,您知道一个对象已被声明为 finalizable,并且可能但不一定是回收的。只有当 finalization 完成并且堆对象的内存是收集的是放置在其关联 ReferenceQueue 上的 WeakReference 或 SoftReference。"
  • @Margaret Bloom 靠近一点,我需要先阅读

标签: java concurrency garbage-collection weak-references


【解决方案1】:

临时引用将保留在堆栈帧的槽中是不正确的,如this answer 中所述。但是,当您在紧密循环中轮询变量时,优化器可能会创建代码来保持多次迭代的引用,而不是每次都从主内存中读取值。由于System.gc() 无论如何只是一个提示,并且您只调用了一次,因此无法保证这个特定的 gc 循环会发现这个对象是不可访问的,如果这个循环发生了。之后,由于没有后续的 GC 循环,您的轮询循环将永远运行。

您还打错了sleep 电话。它会在检测到集合后进入睡眠状态,这是没有意义的。如果你改变了

while (true) {
    if (pRef.get() == null) {
       Thread.sleep(100);
        break;
    }
}

while (true) {
    if (pRef.get() == null) {
        break;
    }
    Thread.sleep(100);
}

它适用于大多数环境。但是你不需要这么复杂的例子:

WeakReference<Object> ref=new WeakReference<>(new Object());
int count=0;
while(ref.get()!=null) {
    System.gc();
    count++;
}
System.out.println("collected after "+count+" polls");

适用于大多数系统。在我的系统上,它会在第一个周期收集对象。

那么轮询ReferenceQueue 和轮询Reference 之间的主要区别是什么?

顾名思义,ReferenceQueue 是一个队列,允许处理多个引用。您可以轮询ReferenceQueue 以获取第一个收集的引用(如果有的话),而不是一个接一个地轮询数千个引用以查明其中一个是否有收集的引用。如果它们都没有被收集,那么您在第一次投票后就完成了。

因此,引用队列的轮询成本与收集对象的数量有关,而不是与现有对象的总数有关。一个典型的例子是WeakHashMap。收集到键后,必须从后备数组中删除仍具有对该值的引用的关联条目。无需遍历整个数组,检查每个条目是否存在密钥,只需轮询引用队列以获取收集的密钥。由于扩展引用对象已经记住了哈希码,所以可以在O(1)时间的数组中找到它。这是至关重要的,因为没有任何人必须调用的专用清理方法。由于它像普通地图一样工作,只是具有弱键语义,因此轮询在该地图上的每个方法调用上透明地发生。所以它一定很快。

【讨论】:

    【解决方案2】:

    只需稍加修改,您的代码就会产生预期的结果。请参阅下面的代码 sn-p。

    在你的代码中,一行

    pRef.get() == null
    

    会将pRef.get() 分配给方法run() 的框架中的临时槽。条件计算后的事件,槽不自动清空。

    垃圾收集器将堆栈上活动帧中的所有槽/局部变量视为 GC 根。您无意中创建了对对象的强引用,因此它不会被清除。

    我修改了版本,我已将pRef.get() 移至嵌套方法。一旦执行从方法返回,它的框架就会被释放,所以对对象的引用仍然是为了防止 GC 收集它。

    当然,如果 JVM 重新编译 run() 方法并内联 isRefernceCollected(pRef) 调用,它可能会再次中断。

    总而言之,引用队列为您提供了处理引用的确定性和有效的方法。池化可以工作,但它很脆弱,并且依赖于 javac 和 JVM JIT 的代码编译。

    修改后的代码sn-p。

    public class TestPhantomRefQueue {
    
        public static void main(String[] args)
                throws InterruptedException {
    
            Object obj = new Object();
            final ReferenceQueue queue = new ReferenceQueue();
    
            final WeakReference pRef =
                    new WeakReference(obj, queue);
    
            obj = null;
    
            new Thread(new Runnable() {
                public void run() {
                    try {
                        System.out.println("Awaiting for GC");
    
                        while (true) {
                            if (isRefernceCollected(pRef)) {
                               Thread.sleep(100);
                                break;
                            }
                        }
                        System.out.println("Referenced GC'd");
                        System.out.println(pRef.get());
    
    
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
    
                protected boolean isRefernceCollected(final WeakReference pRef) {
                    return pRef.get() == null;
                }
            }).start();
    
            // Wait for 2nd thread to start
            Thread.sleep(2000);
    
            System.out.println("Invoking GC");
            System.gc();
        }
    }
    

    输出

    Awaiting for GC
    Invoking GC
    Referenced GC'd
    null
    

    【讨论】:

    • 另外,正如 EJP 注意到的,如果放置 Thread.sleep(100);在睡眠之前,代码在我的电脑上正常工作。谢谢,因此我明白为什么我不应该轮询参考。 Jit 的优化程度超出了我们的预期。
    【解决方案3】:

    只要原始引用可用于程序的任何部分,它就不会被垃圾回收。访问原始引用以检查它是否为空将阻止对象被释放。这就是为什么您需要特殊的引用类型来处理这种情况。

    【讨论】:

    • 我是关于WeakReference ref = new WeakReference (new Object()) ref
    猜你喜欢
    • 1970-01-01
    • 2021-10-25
    相关资源
    最近更新 更多