为什么 Java 中的一些资源没有被垃圾回收,必须关闭或自动关闭?
Why are some some resources in Java not garbage collected and must be closed or autoclosed?
如果你幸运的话,其中一些 类 实现了 AutoClosable,但有时你只需要小心并检查现有的方法,就会注意到有一个 close
,destroy
或 shutdown
方法(或作者决定命名的任何方法)。
这是 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
或其他方式)处理资源,但这还不够好。
如果你幸运的话,其中一些 类 实现了 AutoClosable,但有时你只需要小心并检查现有的方法,就会注意到有一个 close
,destroy
或 shutdown
方法(或作者决定命名的任何方法)。
这是 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
或其他方式)处理资源,但这还不够好。