为什么 Java 中的一些资源没有被垃圾回收,必须关闭或自动关闭?

Why are some some resources in Java not garbage collected and must be closed or autoclosed?

如果你幸运的话,其中一些 类 实现了 AutoClosable,但有时你只需要小心并检查现有的方法,就会注意到有一个 closedestroyshutdown 方法(或作者决定命名的任何方法)。

这是 Java 中资源泄漏的主要来源。

我和一位同事讨论过这个问题,我也想知道:为什么这不能以某种方式自动化?

理论上这种情况可以使用finalize,但实际上是not recommended。那么为什么没有办法只使用其中一些可关闭的资源并让 GC 在实例不再可用时自动关闭它们而不必记住明确地编写一些 close 处理代码(如 try ...)?

这是因为在 GC 启动之前系统可能资源匮乏(文件描述符,...)吗?

注意:我尽可能使用自动关闭并使用 FindBugs(+ FB contrib)检查我的代码是否存在内存泄漏,但我仍然想知道...

同样感兴趣(如答案中所述):deprecation of finalize

why this cannot be automated in some way ?

因为,一般来说,class 可以做任何事情:编译器没有简单的方法可以知道某些东西是 "opened",因此应该是 "closed",因为Java 没有强烈的价值观所有权概念。

即使你有一个类型需要关闭的字段,你也不能轻易保证它不会从 getter 返回给其他负责的东西关闭它。

指示 class 实例需要关闭的唯一真正一致的方法是实现 AutoCloseable 接口(或 Closeable,或扩展它的其他接口)。如果您正在使用这些,IDE 可能会提供有关泄漏资源的警告;但这些将以 best-effort 为基础。

垃圾收集器的唯一工作是收集不再使用的内存。添加关闭资源将对垃圾收集器的性能产生负面影响,目前由垃圾收集器调用的 Finalizer 线程完成,以便允许实现在收集之前清除资源。值得注意的是,宣布此机制 deprecated 是因为它从一开始就不是解决此类问题的最佳解决方案,但目前可以实施您的 classes 来清理自己在收集它们之前。

Finalizer(或 Java 9 中的新机制)可能会被扩展以检查要收集的 class 是否实现了 AutoClosable(添加了Java 1.7,所以它不是那么旧)并在 finalize 之外调用它。这将产生与您建议的效果类似的效果,而无需更改垃圾收集器的行为和角色。也许这已经发生了(我自己还没有测试过)。

如果您正在开发一个公共模块(或一个公共工具class),您可以使用execute around method pattern来处理需要关闭的资源。所以这样你的模块的用户就不必处理这些资源的关闭。(可能会防止很多错误,因为人们可能会忘记关闭资源。)

Venkat 先生的演讲很棒,他谈到了这个问题。看接下来的 10 分钟,他解释得很漂亮。https://youtu.be/e4MT_OguDKg?t=49m48s

这是演示文稿中的示例代码;

public class Resource {
    /*
     * This represents a resource that needs to be closed.
     * Since opening and closing the resource are done through use()  method,
     * Users of this resource don't have to care about resource is being closed or not.
     * They just have to pass operations that they want to execute on the resource.
     */
    private Resource() {System.out.println("created..");}
    public Resource op1() {System.out.println("op1");return this;}
    public Resource op2() {System.out.println("op2");return this;}
    private void close() {System.out.println("closed..");}

    public static void use(Consumer<Resource> consumer) {
        Resource resource = new Resource();
        try {
            consumer.accept(resource);
        }
        finally {
            resource.close();
        }
    }
}

public class SampleResourceUser {
    /*
     * This represents the user of the Resource,
     * User only cares about which operations that needs to be done on the resource.
     * Opening and closing the resource wrapped around the operation methods by the owner of the Resource.
     * 
     */
    public static void main(String[] args) {
        Resource.use(resource->resource.op1().op2());
    }
}

如果愿意,您可以创建自己的 auto-close 资源,但您需要做一些工作。

您可以编写一个 manager/factory class 来保持对代表每个可关闭对象的对象的弱引用。您将这个 "Representative" 门面 class 交给客户,客户使用这个代表 class 来访问资源(它将包含对 closable 的引用并充当委托)。

这意味着工厂启动一个线程并保留一个映射:

<WeakReference<Representative>, Closable> 

它可以迭代。如果代表已被垃圾收集(WeakReference 将 return 为 null),关闭 closable 并将其从地图中删除——收集中留下的任何东西也可以在 VM 关闭时使用钩子关闭——这是可能是最危险的部分,因为线程在关闭期间仍可能与 Closable 交互,但我们拥有解决问题的所有工具。

您的可关闭资源本身永远不会被它的代表和 Manager/Factory 之外的任何东西持有,所以它的行为是可以预测的。

我从来没有见过这样做的——可能是因为它看起来比仅仅制作一个对象要多得多"Closable"(而且更容易错误地实现),但我不知道为什么它不起作用。

Java 将此作为 pattern/language 功能来实现是相当困难的,但是通过为我们提供 WeakReference,它为我们提供了使之成为可能的工具。

在我看来,所有答案似乎都缺少要点:虽然 GC 可以处理资源关闭,但速度不够快。

一个例子是尚未解决的内存映射文件问题。当它们不再引用它们时,它们的映射将被清除,但与此同时,您可能 运行 文件描述符或虚拟内存不足(这确实可能发生,因为它被限制在几个 TB)。

  • 只有在堆内存耗尽时才会调用 GC,这可能在其他资源耗尽之后很长时间。
  • 一旦对象变得不可访问,就不可能有效地收集垃圾。这将需要引用计数,这比分代 GC 慢得多,并且需要对循环引用进行一些额外处理。
  • 在一些分离的 GC 周期中不可能有效地收集资源,因此资源关闭工作得足够快。

这就是恕我直言 错误的原因。您可以通过这种方式(或使用 finalize 或其他方式)处理资源,但这还不够好。