Почему мои объекты не умирают?

Я пытаюсь реализовать механизм, который удаляет кэшированные файлы, когда объекты, которые их содержат, умирают, и решил использовать 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();
        }
    }
}

Выход:

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 Сохранить изображение в файл: <>\AppData\Local\Temp\tmp7..0.PNG

2013-08-05 22: 35: 03,379 DEBUG Удаление неиспользуемого файла: <>\AppData\Local\Temp\tmp7..0.PNG создан в понедельник, 05 августа, 22:35:02 IDT 2013

1 ответ

Решение

Ответ в том, что в вашем примере 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

Это означает, что все, что вы хотели сделать со справочной очередью, будет сделано, когда элемент еще жив.

Другие вопросы по тегам