08.避免使用Finalizer和cleaner机制

避免使用Finalizer和cleaner机制

机制缺点

Finalizer 和 Cleaner 机制的一个缺点是不能保证他们能够及时执行。在一个对象变得无法访问时,到Finalizer 和 Cleaner 机制开始运行时,这期间的时间是任意长的。 这意味着你永远不应该 Finalizer 和 Cleaner 机制做任何时间敏感(time-critical)的事情。例如,依赖于 Finalizer 和 Cleaner 机制来关闭文件是严重的错误,因为打开的文件描述符是有限的资源。 如果由于系统迟迟没有运行 Finalizer 和 Cleaner 机制而导致许多文件被打开,程序可能会失败,因为它不能再打开文件了。

Finalizer 机制的另一个问题是在执行 Finalizer 机制过程中,未捕获的异常会被忽略,并且该对象的 Finalizer 机制也会终止 [JLS, 12.6]。未捕获的异常会使其他对象陷入一种损坏的状态(corrupt state)。如果另一个线程试图使用这样一个损坏的对象,可能会导致任意不确定的行为。通常情况下,未捕获的异常将终止线程并打印堆栈跟踪(stacktrace),但如果发生在 Finalizer 机制中,则不会发出警告。Cleaner 机制没有这个问题,因为使用 Cleaner 机制的类库可以控制其线程。

  • finalizer机制不能保证及时的运行。finalizer的执行时机受虚拟机调度控制,其执行时机是不可知的。因此,如果在 finalize() 中关闭文件流,可能出现由于 finlizer() 方法迟迟没有调用而导致存在大量没有关闭的文件流。
  • finalizer机制是垃圾收集算法的功能,在不同的jvm上可能存在不同的实现。因此,可能出现开发环境和生产环境产生结果不一致的现象。
  • 当在 finalize() 方法中执行某些操作抛出了异常之后,没有正确的处理这些异常,则可能导致出现资源的泄露或者使对象陷入一种损坏的状态。如下面的代码,在 finalize() 方法中执行关闭文件流,但是实际执行的时候出现异常并且抛出给虚拟机。这时候,文件流没有被正确的关闭,但是系统中又不存在新的入口用于关闭这个文件流,因此这里存在一个文件的资源泄漏。
public class Resource {
    private File file;
    private FileInputStream fileInputStream;

    public Resource(String filePath) throws FileNotFoundException {
        file = new File(filePath);
        fileInputStream = new FileInputStream(file);
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            // 模拟一个错误
            if (true) {
                throw new Exception("Something went wrong during cleanup!");
            }
            fileInputStream.close(); // 本应关闭文件句柄
            System.out.println("File stream closed successfully.");
        } catch (Exception e) {
            System.err.println("An error occurred during cleanup: " + e.getMessage());
            throw e; // 重新抛出异常
        }
    }

    public static void main(String[] args) {
        try {
            Resource resource = new Resource("example.txt");
            resource = null; // 使对象变得可回收
            System.gc(); // 请求垃圾回收
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

类似文章