清理与Object关联的外部资源的可靠方法

Dur*_*dal 8 java garbage-collection

具体用例:二进制数据有一个抽象,广泛用于处理任意大小的二进制blob.由于抽象是在没有关于VM 之外的事情的情况下创建的,因此现有实现依赖于垃圾收集器的生命周期.

现在我想添加一个使用堆外存储的新实现(例如在临时文件中).由于存在许多使用抽象的现有代码,因此引入用于显式生命周期管理的其他方法是不切实际的,我不能使用每个客户端用例来重写以确保它们管理新的生命周期要求.

我可以想到两种解决方案,但无法确定哪种方法更好:

a.)使用finalize()来管理相关资源的生命周期(例如,在finalize中删除临时文件.这似乎很容易实现.

b.)使用引用队列和java.lang.Reference(但是哪一个,弱或幻像?)和一些额外的对象,在引用入队时删除文件.这似乎需要更多的工作来实现,我需要不仅创建新的实现,而是分离其清理数据确保清理对象在暴露给用户的对象之前不能进行GC .

c.)我还没有其他方法吗?

我应该采取哪种方法(为什么我更喜欢它)?也欢迎实施提示.


编辑:所需的可靠性程度 - 对于我的目的,如果在VM突然终止时清除临时文件,则完全正常.主要关注的是,当VM运行时,它可以很好地填充本地磁盘(在几天的过程中)与临时文件(这在我身上发生了真正的apache TIKA,它在提取文本时创建了临时文件从某些文档类型来看,zip文件是我认为的罪魁祸首.我在机器上安排了定期清理工作,因此如果文件因清理而丢失,则并不意味着世界末日 - 只要它在短时间内不会定期发生.

据我所知,finalize()适用于Oracale JRE.如果我正确解释了javadocs,引用必须按照文档记录工作(在抛出OutOfMemoryError之前,不能清除只有软/弱可达的引用对象).这意味着当VM可能决定不长时间回收特定对象时,它必须在堆满时最新.反过来,这意味着堆上只存在有限数量的基于文件的blob.VM必须在某些时候清理它们,否则它将完全耗尽内存.或者是否有任何漏洞允许VM在没有清除引用的情况下运行OOM(假设它们不再被严格引用)?


Edit2:就此而言,就此而言,finalize()和Reference应该足够可靠,但我收集Reference可能是更好的解决方案,因为它与GC的交互无法恢复死对象,从而无法恢复其性能影响应该少些?


编辑3:依赖于VM终止或启动(关闭挂钩或类似)的解决方案方法对我没用,因为通常VM会运行很长一段时间(服务器环境).

ksc*_*eid 3

以下是有效 Java中的相关内容:避免终结器

该项目中包含建议按照@delnan 在评论中建议的方式进行操作:提供显式终止方法。还提供了大量示例:InputStream.close()Graphics.dispose()等。了解奶牛可能已经离开了那个牛棚......

无论如何,这里是如何使用参考对象来实现这一点的草图。首先是二进制数据的接口:

import java.io.IOException;

public interface Blob {
    public byte[] read() throws IOException;
    public void update(byte[] data) throws IOException;
}
Run Code Online (Sandbox Code Playgroud)

接下来是基于文件的实现:

import java.io.File;
import java.io.IOException;

public class FileBlob implements Blob {

    private final File file;

    public FileBlob(File file) {
        super();
        this.file = file;
    }

    @Override
    public byte[] read() throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void update(byte[] data) throws IOException {
        throw new UnsupportedOperationException();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,创建和跟踪基于文件的 blob 的工厂:

import java.io.File;
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class FileBlobFactory {

    private static final long TIMER_PERIOD_MS = 10000;

    private final ReferenceQueue<File> queue;
    private final ConcurrentMap<PhantomReference<File>, String> refs;
    private final Timer reaperTimer;

    public FileBlobFactory() {
        super();
        this.queue = new ReferenceQueue<File>();
        this.refs = new ConcurrentHashMap<PhantomReference<File>, String>();
        this.reaperTimer = new Timer("FileBlob reaper timer", true);
        this.reaperTimer.scheduleAtFixedRate(new FileBlobReaper(), TIMER_PERIOD_MS, TIMER_PERIOD_MS);
    }

    public Blob create() throws IOException {
        File blobFile = File.createTempFile("blob", null);
        //blobFile.deleteOnExit();
        String blobFilePath = blobFile.getCanonicalPath();
        FileBlob blob = new FileBlob(blobFile);
        this.refs.put(new PhantomReference<File>(blobFile, this.queue), blobFilePath);
        return blob;
    }

    public void shutdown() {
        this.reaperTimer.cancel();
    }

    private class FileBlobReaper extends TimerTask {
        @Override
        public void run() {
            System.out.println("FileBlob reaper task begin");
            Reference<? extends File> ref = FileBlobFactory.this.queue.poll();
            while (ref != null) {
                String blobFilePath = FileBlobFactory.this.refs.remove(ref);
                File blobFile = new File(blobFilePath);
                boolean isDeleted = blobFile.delete();
                System.out.println("FileBlob reaper deleted " + blobFile + ": " + isDeleted);
                ref = FileBlobFactory.this.queue.poll();
            }
            System.out.println("FileBlob reaper task end");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,一个测试,其中包括一些人工 GC“压力”以使事情顺利进行:

import java.io.IOException;

public class FileBlobTest {

    public static void main(String[] args) {
        FileBlobFactory factory = new FileBlobFactory();
        for (int i = 0; i < 10; i++) {
            try {
                factory.create();
            } catch (IOException exc) {
                exc.printStackTrace();
            }
        }

        while(true) {
            try {
                Thread.sleep(5000);
                System.gc(); System.gc(); System.gc();
            } catch (InterruptedException exc) {
                exc.printStackTrace();
                System.exit(1);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这应该产生一些输出,例如:

FileBlob reaper task begin
FileBlob reaper deleted C:\WINDOWS\Temp\blob1055430495823649476.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob873625122345395275.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob4123088770942737465.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob1631534546278785404.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob6150533076250997032.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob7075872276085608840.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob5998579368597938203.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob3779536278201681316.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob8720399798060613253.tmp: true
FileBlob reaper deleted C:\WINDOWS\Temp\blob3046359448721598425.tmp: true
FileBlob reaper task end
Run Code Online (Sandbox Code Playgroud)