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

时间:2018-02-20 16:23:01

标签: java garbage-collection resources finalize

如果您幸运的话,其中一些类会实现AutoClosable,但有时您必须小心并检查现有方法,注意有closedestroy或{{ 1}}方法(或作者决定命名的方法)。

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

我和一位同事正在讨论这件事,并且也想知道:为什么这不能以某种方式实现自动化?

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

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

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

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

5 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:2)

  

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

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

即使你有一个需要关闭的类型的领域,你也不能轻易保证它不会从一个吸气剂返回,比如说是一个负责关闭它的东西。

指示类的实例需要关闭的唯一真正一致的方法是通过实现AutoCloseable接口(或Closeable或其他扩展它的接口)。如果您正在使用这些,IDE可能能够提供有关泄露资源的警告;但这些将是尽最大努力的。

答案 2 :(得分:2)

如果您愿意,可以创建自己的自动关闭资源,但是您需要做一些工作。

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

这意味着工厂启动一个线程并保留一张地图:

<WeakReference<Representative>, Closable> 

它可以迭代。如果代表已被垃圾收集(WeakReference将返回null),关闭可关闭并将其从地图中删除 - 集合中剩余的任何东西也可以在VM关闭时用钩子关闭 - 这可能是最危险的部分因为线程在关机期间仍然可以与Closable进行交互,但是我们拥有管理问题的所有工具。

您的可关闭资源本身从未由其代表和经理/工厂以外的任何人持有,因此其行为是可预测的。

我从来没有见过这样做 - 可能是因为它似乎比制作一个物品更具吸引力而且#34; Closable&#34; (并且更容易实现错误),但我不知道为什么它不起作用。

Java很难将其作为模式/语言功能实现,但通过向我们提供WeakReference,它为我们提供了使其成为可能的工具。

答案 3 :(得分:2)

对我来说,看起来所有答案都缺少重点:虽然GC可以处理资源关闭,但它不能足够快。

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

  • 只有在堆内存耗尽时才会调用GC,这可能在其他资源耗尽后很长时间。
  • 一旦物体无法到达,就无法有效地收集垃圾。这将需要引用计数,这比生成GC慢,并且需要对循环引用进行一些额外的处理。
  • 在某些单独的GC周期中无法有效地收集资源,因此资源关闭的工作速度足够快。

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

答案 4 :(得分:1)

如果您正在开发公共模块(或公共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());
    }
}