为什么我的物体不会死?

Eli*_*ist 6 java garbage-collection phantom-reference

我正在尝试实现一种机制,当持有它们的对象死亡时删除缓存的文件,并决定使用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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

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.
Run Code Online (Sandbox Code Playgroud)

不用说,改变sleep时间,gc多次呼叫等都不起作用.

UPDATE

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

奇怪的是,我有一些完美的代码(只是测试它),虽然它从来没有调用过enqueue.是否有可能ReferenceMap某种方式神奇地将该引用排成一行?

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

一些过滤输出:

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

2013-08-05 22:35:03,379调试删除未使用的文件:<>\AppData\Local\Temp\tmp7..0.PNG创建时间:Aug Aug 05 22:35:02 IDT 2013

A.H*_*.H. 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出将如下所示:

I'm born.
I'm dying!
working with item DeathNotificationObject@6908b095
stopped working with item DeathNotificationObject@6908b095
Run Code Online (Sandbox Code Playgroud)

这意味着当项目仍处于活动状态时,无论您想要如何处理引用队列.