Why won't my objects die?
garbage-collection, java, phantom-reference
Solution
The answer is, that in your example the `PhantomReference` itself is unreachable and hence garbage collected before the referred object itself is garbage collected. So at the time the object is GCed there is no more `Reference` and the GC does not know that it should enqueue something somewhere.
This of course is some kind of head-to-head race :-)
This also explains (without looking to deep into your new code) why putting the reference into some reachable collection makes the example work.
Just for reference (pun intended) here is a modified version of your first example which works (on my machine :-) I just added a set holding all references.
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();
}
}
}
Update
Calling `enqueue` by hand is possible in your example but not in real code. it gives plain wrong result. Let me show by calling `enqueue` in the constructor and using another `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();
}
}
The output will be like this:
I'm born.
I'm dying!
working with item DeathNotificationObject@6908b095
stopped working with item DeathNotificationObject@6908b095
Which means that whatever you wanted to do with the reference queue would be done when the item is still alive.
Problem
I'm trying to implement a mechanism that deletes cached files when the objects that hold them die, and decided to use `PhantomReference`s to get notified on garbage collection of an object. The problem is I keep experiencing weird behavior of the `ReferenceQueue`. When I change something in my code it suddenly doesn't fetch objects anymore. So I tried to make this example for testing, and ran into the same problem: ``` 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(); } } } ``` The output is: ``` 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. ``` Needless to say, changing the `sleep` time, calling `gc` multiple times etc. didn't work. UPDATE As suggested, I called `Reference.enqueue()` of my reference, which solved the problem. The weird thing, is that I have some code that works perfectly (just tested it), although it never calls `enqueue`. Is it possible that putting the `Reference` into a `Map` somehow magically enqueued the reference? ``` 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(); } } ``` Some filtered output: 2013-08-05 22:35:01,932 DEBUG Save image to file: <>\AppData\Local\Temp\tmp7..0.PNG 2013-08-05 22:35:03,379 DEBUG Deleting unused file: <>\AppData\Local\Temp\tmp7..0.PNG created at Mon Aug 05 22:35:02 IDT 2013