清理与对象相关联的外部资源的可靠的方法(Reliable method of cleaning up

2019-06-28 06:47发布

具体的用例:对于二进制数据,它被广泛用于处理任意大小的二进制斑点的抽象。 由于抽象未经创建尽管有关VM 以外的东西,现有的实现依赖于垃圾收集器的生命周期。

现在我想补充一点,采用离堆存储一个新的实现(例如,在一个临时文件)。 由于有大量的现有代码,使用抽象,引入附加的方法明确的生命周期管理是不切实际的,我不能重写使用,以确保他们所管理的新的生命周期的每一个要求客户使用情况。

我能想到的两种解决方法,但不能决定哪一个更好:

一。)使用的finalize()来管理相关资源的生命周期(如临时文件会被敲定删除。这似乎很容易实现。

湾)的参考队列的使用和java.lang.Reference(但哪一个,弱或与删除该文件,当参考排入一些额外的物体影像?)。 这似乎有点更多的工作,我需要创建不仅是新的实现,但分离出其清理数据,并确保清理对象不能是已经暴露给用户的对象之前GC'd 。

角)其他一些方法我没有,虽然吗?

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


编辑:需要reliaility的程度-我的目的了完全正常的,如果万一临时文件清理虚拟机突然终止。 主要关注的是,虽然虚拟机运行时,它可以很好地填补了本地磁盘(超过几天的过程中)与临时文件(这发生在我身上的真实与Apache TIKA,提取文本时创建的临时文件从某些文档类型,zip文件是我相信的罪魁祸首)。 我已安排在机器上定期清理,所以如果一个文件删除由清理并不意味着世界末日 - 只要它不会在很短的时间间隔定期发生。

至于我能决定的finalize()的工作原理与Oracale JRE。 如果我正确地解释的javadoc,参考文献必须作为记录(有没有办法仅轻声/弱可参考对象不OutOfMemoryError异常清除之前抛出)。 这意味着当VM可以决定不回收特定对象的很长一段时间,它有这样做的最新当堆得到充分。 反过来,这意味着只能存在于堆上我的基于文件的斑点的数量有限。 VM具有清除它们在某些时候,或者它会确定耗尽内存。 还是有它允许虚拟机运行不清除引用OOM任何漏洞(假设他们没有stronly refered了)?


EDIT2:据我看到它在这一点上双方敲定()和参考应该是足够可靠的,我的目的,但我收集参考可能是更好的解决方案,因为它与GC相互作用不能复活死亡的对象,因此它的性能影响要少吗?


EDIT3:依靠VM终止或启动(关闭挂钩或类似)解决方案的方法是不是对我有用处,因为通常在虚拟机运行的时间过长(服务器环境)。

Answer 1:

下面是有效的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;
}

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

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

然后,一个工厂来创建和跟踪基于文件的斑点:

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

最后,测试,其中包括一些人工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);
            }
        }
    }
}

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

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


Answer 2:

这是我kschneids基于参考实例后炮制的解决方案(万一有人需要一个一般可用的实现)。 它的记录,应该很容易理解/适应:

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * Helper class for cleaning up resources when an object is
 * garbage collected. Use as follows (both anonymous subclass or
 * public subclass are fine. Be extra careful to not retain
 * a reference to the trigger!):
 * 
 * new ResourceFinalizer(trigger) {
 * 
 *     // put user defined state relevant for cleanup here
 *     
 *     protected void cleanup() {
 *         // implement cleanup procedure.
 *     }
 * }
 *
 * Typical application is closing of native resources when an object
 * is garbage collected (e.g. VM external resources).
 * 
 * You must not retain any references from the ResourceFinalizer to the
 * trigger (otherwise the trigger can never become eligible for GC).
 * You can however retain references to the ResourceFinalizer from the
 * trigger, so you can access the data relevant for the finalizer
 * from the trigger (no need to duplicate the data).
 * There is no need to explicitly reference the finalizer after it has
 * been created, the finalizer base class will ensure the finalizer
 * itself is not eligible for GC until it has been run.
 * 
 * When the VM terminates, ResourceFinalizer that haven't been
 * triggered will run, regardless of the state of their triggers
 * (that is even if the triggers are still reachable, the finalizer
 * will be called). There are no guarantees on this, if the VM
 * is terminated abruptly this step may not take place.
 */
public abstract class ResourceFinalizer {

    /**
     * Constructs a ResourceFinalizer that is triggered when the
     * object referenced by finalizationTrigger is garbage collected.
     * 
     * To make this work, you must ensure there are no references to
     * the finalizationTrigger object from the ResourceFinalizer.
     */
    protected ResourceFinalizer(final Object trigger) {
        // create reference to trigger and register this finalizer
        final Reference<Object> reference = new PhantomReference<Object>(trigger, referenceQueue);
        synchronized (finalizerMap) {
            finalizerMap.put(reference, this);
        }
    }

    /**
     * The cleanup() method is called when the finalizationTrigger
     * has been garbage collected.
     */
    protected abstract void cleanup();

    // --------------------------------------------------------------
    // ---
    // --- Background finalization management
    // ---
    // --------------------------------------------------------------

    /**
     * The reference queue used to interact with the garbage collector.
     */
    private final static ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();

    /**
     * Global static map of finalizers. Enqueued references are used as key
     * to find the finalizer for the referent.
     */
    private final static HashMap<Reference<?>, ResourceFinalizer> finalizerMap =
            new HashMap<Reference<?>, ResourceFinalizer>(16, 2F);

    static {
        // create and start finalizer thread
        final Thread mainLoop = new Thread(new Runnable() {
            @Override
            public void run() {
                finalizerMainLoop();
            }
        }, "ResourceFinalizer");
        mainLoop.setDaemon(true);
        mainLoop.setPriority(Thread.NORM_PRIORITY + 1);
        mainLoop.start();

        // add a shutdown hook to take care of resources when the VM terminates
        final Thread shutdownHook = new Thread(new Runnable() {
            @Override
            public void run() {
                shutdownHook();
            }
        });
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }

    /**
     * Main loop that runs permanently and executes the finalizers for
     * each object that has been garbage collected. 
     */
    private static void finalizerMainLoop() {
        while (true) {
            final Reference<?> reference;
            try {
                reference = referenceQueue.remove();
            } catch (final InterruptedException e) {
                // this will terminate the thread, should never happen
                throw new RuntimeException(e);
            }
            final ResourceFinalizer finalizer;
            // find the finalizer for the reference
            synchronized (finalizerMap) {
                finalizer = finalizerMap.remove(reference);
            }
            // run the finalizer
            callFinalizer(finalizer);
        }
    }

    /**
     * Called when the VM shuts down normally. Takes care of calling
     * all finalizers that haven't been triggered yet.
     */
    private static void shutdownHook() {
        // get all remaining resource finalizers
        final List<ResourceFinalizer> remaining;
        synchronized (finalizerMap) {
            remaining = new ArrayList<ResourceFinalizer>(finalizerMap.values());
            finalizerMap.clear();
        }
        // call all remaining finalizers
        for (final ResourceFinalizer finalizer : remaining) {
            callFinalizer(finalizer);
        }
    }

    private static void callFinalizer(final ResourceFinalizer finalizer) {
        try {
            finalizer.cleanup();
        } catch (final Exception e) {
            // don't care if a finalizer throws
        }
    }

}


Answer 3:

如果你不是特别担心很快清理的文件,然后finalize是要走的路。 谁也不能保证任何特定的对象是有史以来GC'd,即使您运行的内存(VM在理论上可以只收集堆的一部分)。 但是,如果一个对象被GC'd它将被最终确定,所以你知道你将拥有最多的sizeof(堆)/的sizeof(内存句柄)未确定的斑点,这使一些约束你的磁盘使用情况。 这是一个相当薄弱的束缚,但它听起来像它可能对你不够好。



Answer 4:

在紧要关头,只是做在你的终结是个不错的解决方案,将至少关闭您的文件都有一个良好的比例,大概。 如果这是够好,我会走这条路线,因为它会容易得多。

在另一方面,如果你正在寻找,然后使用终结任何肯定很不好; 你不能依赖于它们正在不断运行,更别说及时,而这同样的道理也适用更松散的各种特殊类型的参考过清理。 它,而取决于你的应用程序,硬件的详细信息,但一般而言,您有没有保证之前,你的硬盘填补了文献将被清除。

这是更有可能发生,如果你在内存中保存(这是考虑大部分空间)的数据是巨大的,但非常短暂的,而文件引用持续更长的时间。 这导致大量的小型垃圾回收,这将清除年轻一代的空间,删除死的数据,并最终促使许多文件引用的,但结果没有大的垃圾回收,这将清除旧的终身的对象,如你的文件引用,所以这些都是然后保持活力无限。 看看这更多的GC背景。 您也许能够提高多少您的终结实际上得到通过提高年轻一代的尺寸,以换取稍微慢一些选区的打击。

如果你想要更多的确定性,我想解决这个问题略有不同。 首先,实施终结清理作为一个快速简单的情况下的解决方案。 然后建立一个后备过; 决定你是准备让你的文件占用,比你想到居然使用,监视你使用每隔X分钟的总空间优选基本上更多的空间最大数量,如果越过这势必然后删除选择最老的(由上次写入时间)的文件,例如最早的10%。 这给你一个相当辛苦的上限,你可能可以保持检查频率非常低的位置,因为终结应该有希望赶上大多数问题。

另外一个说明,我认为可能是半相关的是deleteOnExit 。 当他们正在创建将保证他们将被自动删除当JVM成功退出对临时文件调用此。 这确实有缺点 ,但:在JVM必须坚持这个文件的引用,直到它关闭时,它给你留下一个小的内存泄漏(每个文件1K,我相信)。 不知道这是否使得它值得你,但可能会帮助!



文章来源: Reliable method of cleaning up an external resource associated with an Object