【问题标题】:Why are some some resources in Java not garbage collected and must be closed or autoclosed?为什么 Java 中的某些资源没有被垃圾收集,必须关闭或自动关闭?
【发布时间】:2018-07-31 01:55:12
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

  • 主要是因为如果 GC 只需要释放巨大的合并内存块,它可以是愚蠢而快速的。这些资源需要运行代码来进行发布。
  • 关闭可能要执行一些额外的任务,想想关闭网络连接。这涉及延迟。您永远不希望有任何事情来延迟垃圾收集。这就是为什么任何关闭机制都不属于 GC 循环的原因。
  • @Lou Franco 所以你的意思是它通常会产生停顿和不可预测的性能?
  • “当实例不再可访问时让 GC 自动关闭它们”确实如此。例如,FileOutputStreams 在 GC 时关闭。您不能依赖于在任何特定时间发生这种情况。

标签: java garbage-collection resources finalize


【解决方案1】:

垃圾收集器的唯一工作是收集不再使用的内存。添加关闭资源将对垃圾收集器的性能产生负面影响,目前由垃圾收集器调用的Finalizer 线程完成,以允许实现在收集之前清除资源。值得注意的是,这个机制被声明为deprecated,因为它从一开始就不是解决这类事情的最佳解决方案,但暂时可以实现你的类在它们被收集之前清理它们自己.

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

【讨论】:

  • 如果您没有故意关闭包装器AutoClosable,因为您想继续使用底层资源怎么办?想想Scanner 包装System.in。您不应该关闭Scanner,因为这会关闭您的标准输入。让垃圾收集器/终结器自动关闭所有实现AutoClosable 的东西会很糟糕。它还将打开this can of worms...
【解决方案2】:

为什么这不能以某种方式自动化?

因为,一般来说,一个类可以做任何事情:编译器没有简单的方法知道某个东西是“打开的”,因此应该是“关闭的”,因为 Java 没有强烈的值所有权概念。

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

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

【讨论】:

  • 是的,一些现代代码使用 AutoClosable,但我看到的许多类仍然没有。所以你只需要阅读很多文档并小心并寻找像closeshutdowndestroy,......我个人使用FindBugs + contrib detection,这有点帮助,但我从来没有感觉远离这些。
  • 看来,这是Java的某种问题,你可以使用资源,但是系统并没有真正跟踪资源的使用情况,与内存的使用情况相反(这是在许多方面从 C/C++ 向前迈出了一大步)。也许将来会做更多,希望更多的库会实现 AutoCloseable。
  • 对于不理解新机制的旧代码,无能为力。即使我们将您想要的内容添加到 GC,旧代码也不会很好地使用它(GC 怎么知道调用它?)
  • 我不同意使资源行为可预测是不可能的——它可以做到。写了一个更详细的答案。如果不清楚,我可以写一个例子......
  • @BillK 你的答案仍然依赖于使用类的人知道它需要关闭;如果你在代码的任何地方都应用这个,它会变得非常混乱。
【解决方案3】:

您可以根据需要创建自己的自动关闭资源,但您需要做一些工作。

您可以编写一个管理器/工厂类,该类保持对表示每个可关闭对象的对象的弱引用。您将这个“代表”外观类分发给客户端,客户端使用这个代表类来访问资源(它将包含对可关闭对象的引用并充当委托)。

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

<WeakReference<Representative>, Closable> 

它可以迭代。如果代表已被垃圾收集(WeakReference 将返回 null),请关闭可关闭对象并将其从地图中删除——收集中的任何内容也可以在 VM 关闭时用钩子关闭——这可能是最危险的部分原因是线程在关闭期间仍可能与 Closable 交互,但我们拥有所有工具来管理该问题。

您的可关闭资源本身永远不会被其代表和经理/工厂之外的任何人持有,因此它的行为是可预测的。

我从未见过这样做过——可能是因为它看起来比仅仅让一个对象“可关闭”(并且更容易错误地实现)要多得多,但我不知道为什么它不会没用。

Java 很难将其作为一种模式/语言特性来实现,但通过给我们 WeakReference,它为我们提供了使之成为可能的工具。

【讨论】:

  • 似乎有一些解决方案,但整个 Java 社区并没有真正评估它们。 GC 上的大量工作,但对于其他资源,主要是手动工作,祝其他库中使用的一些库好运,泄漏...... AutoClosable 也没有强制执行,所以我们只需要祈祷一些库作者使用它并且开发人员也使用它。
  • @ChristopheRoussy 在“Java”级别上很难做到(对于语言创造者),但我认为你最初的问题是为什么不能由编码人员完成?它可以。你可以用这种方式包装任何你想使用的库,你不受任何语言或库作者的摆布,这不需要任何额外的支持,你只需为你的应用程序自己编写代码。这不是你要求的吗?
  • 我认为您的解决方案不会真正起作用。当您的Representatives 被收集时,可能为时已晚,请参阅my answer
  • @maaartinus 我同意,如果您的资源有限,那么可关闭是唯一的方法——您必须通过代码控制它的使用。如果只是有一个类需要一点清理,这可能会很好。尽管我认为它几乎可以立即拾起大多数掉落的物体,但您的观点是,如果您需要立即恢复它们,则不能依赖它-在这种情况下,可关闭的可能是唯一的方法。
  • @maaartinus 更糟糕的是,对象可能过早被收集。这正是this question 中发生的事情。这种方法的一般问题是,后端对象中发生的实际操作不需要前端,因此,前端可以在操作进行时被收集。原则上,即使是后端对象也可以通过积极优化 JVM 来收集,因为内存管理器只关心对象的内存,而不关心其包含的值(通常为longs)是否真的代表资源……
【解决方案4】:

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

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

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

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

【讨论】:

  • 好点,虽然我不认为我的回答是“错误的”,但您正确地指出,在许多用例中,只有可关闭样式才能起作用。
  • 我认为所有这些答案都比我最近在 SO 上看到的大多数内容更有趣。
【解决方案5】:

如果你正在开发一个公共模块(或一个公共的util类),你可以使用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());
    }
}

【讨论】:

  • 这样的? deadcoderising.com/…,有趣
  • @ChristopheRoussy 是的,我建议你看他的解释,他从各个角度解释了这个问题。
  • 我想我知道这种模式,但 lambda 使其更易于访问。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-04
  • 2010-09-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多