我有一个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()
是正确的方法。有什么建议吗?
哪些情况不会调用finalize()
?如果唯一可能的是退出程序(以预期/意外的方式),我可以接受它,因为这意味着我只冒一个不需要的临时文件(在退出期间处理的那个)的风险。
答案 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);
}
...
}