Използване на 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 е събиран за боклук (в противен случай рискувам да напълня диска с ненужни изображения). Това трябва да се направи, докато приложението все още работи (за разлика от предложенията за почистване по време на прекратяване), поради факта, че е вероятно да работи за дълги периоди.

Не съм напълно запознат с концепцията GC на 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 , наистина имам нужда от някаква колекция, за да предпазя PhantomReferences от самите GC'ed, преди да имам шанса да се поставят в опашка. следователно Картата (обсъдена във вашия коментар по-долу) е необходима. - person Elist; 06.08.2013
comment
@Elist - не, нямате нужда от друга колекция, просто трябва да запазите FileReference в CachedImg. - person jtahlborn; 06.08.2013
comment
За което не се сетих. Сатър. Това (и проблемът извън вътрешния клас, обсъден преди) са причините, поради които ви помолих да направите своя отговор SSCCE, за да бъдете изключени... - person Elist; 06.08.2013
comment
@Elist - да. Мислех, че сглобяването на парчетата ще бъде доста лесно. както и да е, ето го 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
Това не е нещо, което работи в някои случаи, а е, че никога не можете да знаете дали GC ще се изпълнява, преди да изчерпите паметта. Методът 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 от PhantomReferences към екземплярите на моя клас, според тази публикация. Добавям този отговор, защото никой не предложи да се използва 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