Использовать finalize() в моем случае?

У меня есть класс ImageWrapper, который сохраняет изображения во временные файлы на диске, чтобы освободить память кучи, и позволяет перезагружать их при необходимости.

class ImageWrapper {
    File tempFile;
    public ImageWrapper(BufferedImage img) {
        // save image to tempFile and gc()
    }
    public BufferedImage getImage() {
        // read the image from tempFile and return it.
    }
    public void delete() {
        // delete image from disk.
    }
}

Меня беспокоит, как убедиться, что файлы удаляются, когда такой экземпляр ImageWrapper собирает мусор (иначе я рискую заполнить диск ненужными изображениями). Это необходимо выполнять во время работы приложения (в отличие от рекомендаций по очистке во время завершения работы), так как оно может работать в течение длительного времени.

Я не полностью знаком с концепцией сборки мусора в Java, и мне было интересно, является ли finalize() тем, что я ищу. Моя идея состояла в том, чтобы вызвать delete() (в отдельном потоке, если это имеет значение) из переопределенного метода finalize(). Это правильный способ сделать это?

ОБНОВИТЬ:

Я не думаю, что смогу close() объект, как предлагают многие пользователи, из-за того, что каждое такое изображение извлекается в список слушателей, которых я не контролирую, и может сохранить ссылку на объект. единственный раз, когда я уверен, что смогу удалить файл, - это когда нет ссылок, поэтому я подумал, что finalize() - правильный путь. Какие-либо предложения?

ОБНОВЛЕНИЕ 2:

Каковы сценарии, в которых finalize() не будет вызываться? Если единственные возможности - выход из программы (ожидаемым/неожиданным образом), я могу это сделать, потому что это означает, что я рискую только одним ненужным временным файлом, оставшимся не удаленным (тот, который был обработан во время выхода).


person Elist    schedule 03.08.2013    source источник
comment
Не используйте подход finalize; это действительно не дает вам ничего из того, что уже упоминалось в ответах ниже, и имеет свой собственный набор головных болей. Самым простым решением было бы иметь отдельный каталог в каталоге TMP для вашего приложения. Когда ваше приложение запустится, очистите этот каталог. Это потребуется только в том случае, если ваша JVM вымирает нечистым образом. Во всех остальных случаях (JVM нормально завершает работу) deleteOnExit должно работать нормально.   -  person Sanjay T. Sharma    schedule 04.08.2013
comment
Приложение, скорее всего, будет работать длительное время (дни) и постоянно сохранять изображения на диск. Удаление этих файлов должно быть выполнено во время выполнения.   -  person Elist    schedule 04.08.2013
comment
Я думаю, что это важная деталь, которую вы упустили, формулируя свой первоначальный вопрос. В любом случае, в этом случае вам следует использовать подход PhantomReference, как указано в некоторых ответах ниже.   -  person Sanjay T. Sharma    schedule 04.08.2013
comment
Я отредактировал свой вопрос, чтобы отметить эту деталь.   -  person Elist    schedule 04.08.2013


Ответы (6)


Хорошей альтернативой finalize является PhantomReference. лучший способ использовать это:

public class FileReference extends PhantomReference<CachedImage> {
  private final File _file;

  public FileReference(CachedImage img, ReferenceQueue<CachedImage> q, File f) {
    super(img, q);
    _file = f;
  }

  public File getFile() {
    _file;
  }
}

Затем используйте его как:

public class CachedImage {

    private static final ReferenceQueue<CachedImage> 
            refQue = new ReferenceQueue<CachedImage>();

    static {
        Thread t = new Thread() {
            @Override
            public void run() {
                try {
                    while (true) {
                        FileReference ref = (FileReference)refQue.remove();
                        File f = ref.getFile();
                        f.delete();
                    }
                } catch (Throwable t) {
                    _log.error(t);
                }
            }
        };
        t.setDaemon(true);
        t.start();
    }

    private final FileReference _ref;

    public CachedImage(BufferedImage bi, File tempFile) {
        tempFile.deleteOnExit();
        saveAndFree(bi, tempFile);
        _ref = new FileReference<CachedImage>(this, refQue, tempFile);
    }
    ...
}
person jtahlborn    schedule 04.08.2013
comment
Я борюсь с этим часами. Все работает отлично (remove() в while(true), а не poll() в запланированном выполнении), но когда я пытаюсь избавиться от Map и использовать свой расширенный класс, ссылки просто не будут поставлены в очередь... Может ли быть причина, по которой этот метод создает дополнительные сильные ссылки? Любая другая идея? - person Elist; 05.08.2013
comment
@Elist - вам нужно будет показать свой фактический код. вы случайно сделали свой класс FileReference внутренним классом? - person jtahlborn; 05.08.2013
comment
Сначала я сделал... но потом я понял, что это, вероятно, проблема. В любом случае, повторный факторинг не решил проблему. - person Elist; 05.08.2013
comment
Я больше не хочу публиковать код, потому что не думаю, что это место для него. думал, что у вас может быть некоторое представление об этом. возможно, я опубликую новый вопрос позже. - person Elist; 05.08.2013
comment
Как указано в другом вопросе, который я задал об использовании ReferenceQueue , мне нужна некоторая коллекция, чтобы PhantomReference не попали в сборщик мусора, прежде чем они смогут попасть в очередь. следовательно, карта (обсуждаемая в вашем комментарии ниже) необходима. - person Elist; 06.08.2013
comment
@Elist - нет, вам не нужна другая коллекция, вам просто нужно сохранить FileReference в CachedImg. - person jtahlborn; 06.08.2013
comment
О чем я не подумал. Кливер. Это (и проблема, не связанная с внутренним классом, обсуждавшаяся ранее) является той причиной, по которой я попросил вас сделать ваш ответ SSCCE, чтобы получить исключение... - person Elist; 06.08.2013
comment
@Элист - да. Я думал, что собрать части воедино будет довольно просто. в любом случае, вот SSCCE. - person jtahlborn; 06.08.2013
comment
Еще одна вещь - возможно ли, что компилятор Java игнорирует _ref как частный без использования или геттера? - person Elist; 06.08.2013
comment
@Elist - нет, это невозможно. - person jtahlborn; 06.08.2013

Другой подход заключается в использовании файла . deleteOnExit(), который помечает файл для удаления JVM при выходе. Я понимаю, что это не совсем то, что вы ищете, но может быть интересно.

Чтобы было ясно, если ваша JVM неожиданно выйдет из строя, она не очистит эти файлы. Таким образом, вы можете спроектировать свое решение так, чтобы очищать файлы кеша при запуске, чтобы со временем не создавать массу неиспользуемых файлов кеша.

person Brian Agnew    schedule 03.08.2013
comment
Это действительно интересно. Я думаю о комбинации двух (для случаев, когда finalize по какой-то причине был пропущен). По ссылке не понял - гарантировано ли от непредвиденных завершений? - person Elist; 04.08.2013
comment
Конечно, как вы сами сказали, одного этого недостаточно, потому что я хочу почистить диск, пока приложение еще работает. Мне придется дополнительно проверить оба пути (finalize и deleteOnExit), так как я не чувствую, что получил надлежащую информацию о них... - person Elist; 04.08.2013

Не рекомендуется использовать finalize(). Проблема в том, что вы не можете рассчитывать на то, что сборщик мусора когда-либо удалит объект. Таким образом, выполнение любого кода, который вы помещаете в переопределенный метод finalize() вашего класса, не гарантируется.

person Java Panter    schedule 03.08.2013
comment
Можете ли вы уточнить случай, когда finalize нельзя вызывать? Как вы думаете, это должно быть серьезной проблемой в моем случае? - person Elist; 04.08.2013
comment
Это не то, что работает в некоторых случаях, просто вы никогда не можете знать, будет ли работать сборщик мусора, прежде чем у вас закончится память. Метод finalize() для любого данного объекта может работать, но вы не можете рассчитывать на это, поэтому не добавляйте какой-либо существенный код в свой метод finalize(). На самом деле рекомендуется вообще не переопределять finalize(). - person Java Panter; 04.08.2013

Нет никакой гарантии, что ваш метод finalize когда-либо будет вызван; в частности, любые объекты, висящие вокруг при выходе из программы, обычно просто выбрасываются без очистки. Closeable гораздо лучший вариант.

person chrylis -cautiouslyoptimistic-    schedule 03.08.2013

В качестве альтернативы ответу @Brian Agnew, почему бы не установить ShutdownHook, которая очищает каталог кеша?

public class CleanCacheOnShutdown extends Thread {
  @Override
  public void run() { ... }
}

System.getRuntime().addShutdownHook(new CleanCacheOnShutdown());
person Tom G    schedule 04.08.2013
comment
Можно, но я не вижу никаких преимуществ. deleteOnExit() не заставляет меня вести список используемых временных файлов (я предпочитаю использовать File.createTempFile(), который гарантирует, что файлы в какой-то момент будут удалены ОС, а затем использовать мой собственный кеш). - person Elist; 04.08.2013

В итоге я использовал комбинацию File.deleteOnExit() (спасибо @Brian) и ScheduledExecutorService, которая проходит через ReferenceQueue из PhantomReference к экземплярам моего класса, согласно этот пост. Я добавляю этот ответ, потому что никто не предлагал использовать ReferenceQueue (что я считаю идеальным решением для моей проблемы), и я думаю, что это будет полезно для будущих читателей.

Результат (несколько упрощенный) таков (изменил имя класса на CachedImage):

public class CachedImage {
    private static Map<PhantomReference<CachedImage>, File> 
            refMap = new HashMap<PhantomReference<CachedImage >, File>();
    private static ReferenceQueue<CachedImage> 
            refQue = new ReferenceQueue<CachedImage>();

    static {
        Executors.newScheduledThreadPool(1).scheduleWithFixedDelay(new Thread() {
            @Override
            public void run() {
                try {
                    Reference<? extends CachedImage> phanRef = 
                            refQue.poll();
                    while (phanRef != null) {
                        File f = refMap.get(phanRef);
                        f.delete();
                        phanRef = refQue.poll();

                    }
                } catch (Throwable t) {
                    _log.error(t);
                }
            }
        }, 1, 1, TimeUnit.MINUTES);
    }

    public CachedImage(BufferedImage bi, File tempFile) {
        tempFile.deleteOnExit();
        saveAndFree(bi, tempFile);
        PhantomReference<CachedImage> pref = 
                new PhantomReference<CachedImage>(this, refQue);
        refMap.put(pref, tempFile);
    }
    ...
}
person Elist    schedule 04.08.2013
comment
эта версия не является потокобезопасной. см. мой ответ для лучшего использования PhantomReference. - person jtahlborn; 04.08.2013
comment
Почему меня должны беспокоить проблемы безопасности потоков с PhantomReferences? Что может случиться, если объект все равно давно исчез? - person Elist; 04.08.2013
comment
Я никогда не говорил, что проблема связана с PhantomReference. проблема связана с вашим общим HashMap (который в любом случае не нужен, как вы можете видеть в моем ответе). также у вас есть утечка памяти, так как вы никогда не очищаете HashMap. - person jtahlborn; 04.08.2013
comment
+1 за указание на преимущества расширения PhantomReference. и, следовательно, избыточность Map. Если бы вы могли отредактировать свой ответ только как SSCCE (для полноты), он будет принят. - person Elist; 04.08.2013