为什么我的物体不会死?

时间:2013-08-05 19:02:32

标签: java garbage-collection phantom-reference

我正在尝试实现一种机制,当持有它们的对象死亡时删除缓存文件,并决定使用PhantomReference来获取有关对象的垃圾收集的通知。问题是我一直在经历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 DEBUG将图像保存到文件:&lt;&gt; \ AppData \ Local \ Temp \ tmp7..0.PNG

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

1 个答案:

答案 0 :(得分:6)

答案是,在您的示例中,PhantomReference本身无法访问,因此在引用对象本身被垃圾收集之前 之前收集垃圾。因此,当对象被GCed时,不再有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

这意味着当项目仍处于活动状态时,无论您想对引用队列做什么,都将完成。