6

我正在尝试实现一种机制,当保存它们的对象死亡时删除缓存文件,并决定使用PhantomReferences 来获得有关对象垃圾收集的通知。问题是我不断遇到ReferenceQueue. 当我更改代码中的某些内容时,它突然不再获取对象。所以我尝试制作这个示例进行测试,并遇到了同样的问题:

public class DeathNotificationObject {
    private static ReferenceQueue<DeathNotificationObject> 
            refQueue = new ReferenceQueue<DeathNotificationObject>();

    static {
        Thread deathThread = new Thread("Death notification") {
            @Override
            public void run() {
                try {
                    while (true) {
                        refQueue.remove();
                        System.out.println("I'm dying!");
                    }
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        };
        deathThread.setDaemon(true);
        deathThread.start();
    }

    public DeathNotificationObject() {
        System.out.println("I'm born.");
        new PhantomReference<DeathNotificationObject>(this, refQueue);
    }

    public static void main(String[] args) {
        for (int i = 0 ; i < 10 ; i++) {
            new DeathNotificationObject();                  
        }
        try {
            System.gc();    
            Thread.sleep(3000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出是:

I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.

不用说,更改时间,多次sleep调用等都不起作用。gc

更新

正如建议的那样,我打电话Reference.enqueue()给我的参考资料,这解决了这个问题。

奇怪的是,我有一些可以完美运行的代码(刚刚测试过),尽管它从不调用enqueue. 是否有可能将参考Reference放入一个Map神奇的队列中?

public class ElementCachedImage {
    private static Map<PhantomReference<ElementCachedImage>, File> 
            refMap = new HashMap<PhantomReference<ElementCachedImage>, File>();
    private static ReferenceQueue<ElementCachedImage> 
            refQue = new ReferenceQueue<ElementCachedImage>();

    static {
        Thread cleanUpThread = new Thread("Image Temporary Files cleanup") {
            @Override
            public void run() {
                try {
                    while (true) {
                        Reference<? extends ElementCachedImage> phanRef = 
                                refQue.remove();
                        File f = refMap.remove(phanRef);
                        Calendar c = Calendar.getInstance();
                        c.setTimeInMillis(f.lastModified());
                        _log.debug("Deleting unused file: " + f + " created at " + c.getTime());
                        f.delete();
                    }
                } catch (Throwable t) {
                    _log.error(t);
                }
            }
        };
        cleanUpThread.setDaemon(true);
        cleanUpThread.start();
    }

    ImageWrapper img = null;

    private static Logger _log = Logger.getLogger(ElementCachedImage.class);

    public boolean copyToFile(File dest) {
        try {
            FileUtils.copyFile(img.getFile(), dest);
        } catch (IOException e) {
            _log.error(e);
            return false;
        }
        return true;
    }

    public ElementCachedImage(BufferedImage bi) {
        if (bi == null) throw new NullPointerException();
        img = new ImageWrapper(bi);
        PhantomReference<ElementCachedImage> pref = 
                new PhantomReference<ElementCachedImage>(this, refQue);
        refMap.put(pref, img.getFile());

        new Thread("Save image to file") {
            @Override
            public void run() {
                synchronized(ElementCachedImage.this) {
                    if (img != null) {
                        img.saveToFile();
                        img.getFile().deleteOnExit();
                    }
                }
            }
        }.start();
    }
}

一些过滤后的输出:

2013-08-05 22:35:01,932 调试将图像保存到文件:<>\AppData\Local\Temp\tmp7..0.PNG

2013-08-05 22:35:03,379 调试删除未使用的文件:<>\AppData\Local\Temp\tmp7..0.PNG 创建于 2013 年 8 月 5 日星期一 22:35:02 IDT

4

1 回答 1

6

答案是,在您的示例中,PhantomReference它本身是不可访问的,因此在被引用的对象本身被垃圾收集之前就被垃圾收集了。因此,在对象被 GC 时,没有更多对象了Reference,并且 GC 不知道它应该在某处排队。

这当然是某种正面交锋:-)

这也解释了(无需深入研究您的新代码)为什么将引用放入某个可访问的集合中会使示例工作。

仅供参考(双关语),这里是您的第一个示例的修改版本(在我的机器上 :-) 我刚刚添加了一个包含所有引用的集合。

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.HashSet;
import java.util.Set;

public class DeathNotificationObject {
    private static ReferenceQueue<DeathNotificationObject> refQueue = new ReferenceQueue<DeathNotificationObject>();
    private static Set<Reference<DeathNotificationObject>> refs = new HashSet<>();

    static {
        Thread deathThread = new Thread("Death notification") {
            @Override
            public void run() {
                try {
                    while (true) {
                        Reference<? extends DeathNotificationObject> ref = refQueue.remove();
                        refs.remove(ref);
                        System.out.println("I'm dying!");
                    }
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            }
        };
        deathThread.setDaemon(true);
        deathThread.start();
    }

    public DeathNotificationObject() {
        System.out.println("I'm born.");
        PhantomReference<DeathNotificationObject> ref = new PhantomReference<DeathNotificationObject>(this, refQueue);
        refs.add(ref);
    }

    public static void main(String[] args) {
        for (int i = 0 ; i < 10 ; i++) {
            new DeathNotificationObject();                  
        }
        try {
            System.gc();    
            Thread.sleep(3000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

更新

在您的示例中可以手动调用enqueue,但在实际代码中则不行。它给出了明显错误的结果。让我通过调用enqueue构造函数并使用另一个来展示main

public DeathNotificationObject() {
    System.out.println("I'm born.");
    PhantomReference<DeathNotificationObject> ref = new PhantomReference<DeathNotificationObject>(this, refQueue);
    ref.enqueue();
}

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

    for (int i = 0 ; i < 5 ; i++) {
        DeathNotificationObject item = new DeathNotificationObject();

        System.out.println("working with item "+item);
        Thread.sleep(1000);
        System.out.println("stopped working with item "+item);
        // simulate release item
        item = null;
    }

    try {
        System.gc();    
        Thread.sleep(3000); 
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

输出将是这样的:

I'm born.
I'm dying!
working with item DeathNotificationObject@6908b095
stopped working with item DeathNotificationObject@6908b095

这意味着您想对引用队列执行的任何操作都将在该项目仍然存在时完成。

于 2013-08-05T20:38:05.600 回答