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

Original source