在我的情况下使用finalize()?

时间:2013-08-03 21:08:45

标签: java garbage-collection finalizer

我有一个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的GC概念并不完全熟悉,我想知道finalize()是否是我正在寻找的。我的想法是从覆盖的finalize()方法调用delete()(在一个单独的Thread上)。这是正确的方法吗?

更新

我不认为我可以close()许多用户建议的对象,因为每个这样的图像都被提取到我无法控制的侦听器列表,并且可能保存一个引用对象。我确定能够删除文件的唯一时间是没有引用,因此我认为finalize()是正确的方法。有什么建议吗?

更新2:

哪些情况不会调用finalize()?如果唯一可能的是退出程序(以预期/意外的方式),我可以接受它,因为这意味着我只冒一个不需要的临时文件(在退出期间处理的那个)的风险。

6 个答案:

答案 0 :(得分:6)

另一种方法是使用File.deleteOnExit()标记JVM在退出时删除的文件。我意识到它不是相当你在寻找什么,但可能会引起人们的兴趣。

要明确的是,如果您的JVM意外死亡,将不会清除这些文件。因此,您可能希望构建解决方案以在启动时清除缓存文件 ,这样就不会随着时间的推移而构建大量未使用的缓存文件。

答案 1 :(得分:3)

不建议使用finalize()。问题是您不能指望垃圾收集器永远删除对象。因此,您放入类的重写finalize()方法的任何代码都不能保证运行。

答案 2 :(得分:3)

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);
    }
    ...
}

答案 3 :(得分:2)

无法保证您的finalize方法会被调用;特别是,当程序退出时,任何闲置的对象通常都会被丢弃而没有清理。 Closeable是一个更好的选择。

答案 4 :(得分:0)

作为@Brian Agnew的答案的替代方案,为什么不安装清除缓存目录的ShutdownHook

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

System.getRuntime().addShutdownHook(new CleanCacheOnShutdown());

答案 5 :(得分:0)

我最终使用了File.deleteOnExit()(感谢@Brian)和ScheduledExecutorService的{​​{1}}组合ReferenceQueue PhantomReference到我的班级实例,到this post。 我添加了这个答案,因为没有人建议使用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);
    }
    ...
}