【问题标题】:Does JVM automatically closes files?JVM会自动关闭文件吗?
【发布时间】:2017-12-12 10:09:24
【问题描述】:

在某处我读到没有必要自己关闭它,只需离开它,JVM 会帮你做到这一点。这是真的吗?

假设我需要从文件中获取数据

Source.fromFile(fileName).getLines() 

直接,没有

val source = Source.fromFile(fileName)
val lines = source.getLines()
............
source.close()

在第一种方式中,我无法直接访问源并关闭它。我的 JVM 工作了很长时间。我需要关闭一个文件(需要关闭未使用的资源)。

如果有人可以在这里留下一些链接或解释,那就太好了。

【问题讨论】:

  • “在某处我读到没有必要自己关闭它,就离开它”不,这就是存在显式关闭方法和 try-with-resources 的原因。
  • @saifahmad - 不正确。它们将最终被 GC 收集......在它们变得无法访问之后。问题是“最终”。如果你依赖 GC 来关闭文件,那么在 GC 有机会清理你的烂摊子之前,你的应用程序很可能会用完 文件描述符。
  • @Stephen C 但是 JVM 足够智能,可以检测到文件描述符将被填充。而 GC 将启动。但我同意让文件指针悬空是一种不好的做法。
  • @saifahmad - 我很确定如果文件描述符用完,GC 不会被触发。 (而且我只是看了一下 OpenJDK Java 8 源代码……)

标签: java scala file jvm


【解决方案1】:

在某处我读到没有必要自己关闭它,只需离开它,JVM 会帮你做到这一点。这是真的吗?

部分正确。

如果你打开一个文件,使用它,然后删除文件或流句柄(或任何你想调用的东西),并且 GC 找到它,那么 GC 会将文件句柄对象排队等待完成。当完成时,文件处理程序的finalize() 方法将释放资源;即文件描述符。

但是,依靠 GC 来执行此操作是一个坏主意。

  • 如果文件句柄可访问,则 GC 不会完成它。
  • 您有办法知道 GC 下一次运行的时间1。
  • 而且当 GC 运行时,它不一定会收集所有的垃圾2。
  • 排队等待完成的对象直到 GC 完成后才会真正完成。 (见@Holger 的评论)

将这四件事放在一起,应用程序很容易在 GC 开始收集和关闭废弃文件句柄之前用完文件描述符。如果发生这种情况,您可能会在打开文件、目录、套接字等操作时遇到异常。

这是一个您可以运行的示例(在 Linux / UNIX 上)以查看这种情况:

import java.io.FileInputStream;

public class Test {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 100000; i++) {
            new FileInputStream("/etc/motd");
        }
    }
}

$ javac Test.java 
$ java Test 
Exception in thread "main" java.io.FileNotFoundException: /etc/motd (Too many open files)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at Test.main(Test.java:6)

1 - 典型的 GC 仅在堆(或堆的一部分)达到给定的“充满度”阈值时运行。如果应用程序没有分配很多对象,则可能需要很长时间才能达到阈值。

2 - 现代 JVM 使用分代垃圾收集器,它们以不同的速率收集堆的不同部分。

【讨论】:

  • 垃圾收集和终结存在常见的混淆。垃圾收集器永远不会调用finalize(),在最好的情况下,它将enqueue 需要终结的对象。由于 99% 的对象不需要终结,JVM 并不关心等待终结的队列不断增长。对于快速分配新资源的应用程序尤其如此,因为这些应用程序线程具有比终结线程更高的优先级。因此,即使如果垃圾收集器已识别封装描述符的可终结对象,JVM 仍可能用完空闲描述符
【解决方案2】:

是的,在某些时候确实如此,JVM 将在一段时间内通过调用finalize 方法帮助释放文件描述符(您可以尝试通过System.gc() 进行验证)。

并且完全同意我们需要在不需要时显式关闭InputStream 并释放文件描述符。

但是如果您的应用程序不经常对文件描述符(文件、网络 io)进行操作,我们是否也需要关注这一点?毕竟,有finalize 方法。

是的,close 它是一个很好的做法,明确释放它。

对于 OP 的问题很像 Java 的:如何通过Files.lines 安全地关闭Stream。在 Java 中,我们可以使用 try with resources 来处理这个问题。但是 Scala 不支持这个,也许你可以尝试用try ... finally 来处理这个,比如:

val reader = Source.fromFile("build.sbt").bufferedReader()
try {
  reader.lines().forEach(line => {
  ...
  })
} finally {
  reader.close()
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多