为什么我的物体不会死?

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

 public class DeathNotificationObject { private static ReferenceQueue refQueue = new ReferenceQueue(); 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(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等都不起作用。

UPDATE

按照建议,我调用了我的引用的Reference.enqueue() ,它解决了这个问题。

奇怪的是,我有一些完美的代码(只是测试它),虽然它从来没有调用入enqueue 。 是否有可能将Reference放入某个Map以某种方式神奇地将该引用排入队列?

 public class ElementCachedImage { private static Map<PhantomReference, File> refMap = new HashMap<PhantomReference, File>(); private static ReferenceQueue refQue = new ReferenceQueue(); static { Thread cleanUpThread = new Thread("Image Temporary Files cleanup") { @Override public void run() { try { while (true) { Reference 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 pref = new PhantomReference(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将图像保存到文件: \ 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

答案是,在您的示例中, 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 refQueue = new ReferenceQueue(); private static Set> refs = new HashSet<>(); static { Thread deathThread = new Thread("Death notification") { @Override public void run() { try { while (true) { Reference 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 ref = new PhantomReference(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 ref = new PhantomReference(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 

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