当流未显式关闭时,Files.list(Path dir) 中的资源泄漏?

2022-09-01 09:54:31

我最近写了一个小应用程序,定期检查目录的内容。过了一会儿,由于打开的文件句柄太多,应用程序崩溃了。经过一些调试,我在以下行中发现了错误:

Files.list(Paths.get(destination)).forEach(path -> {
     // To stuff
});

然后我检查了javadoc(我可能应该更早地这样做)并发现:Files.list

* <p> The returned stream encapsulates a {@link DirectoryStream}.
* If timely disposal of file system resources is required, the
* {@code try}-with-resources construct should be used to ensure that the
* stream's {@link Stream#close close} method is invoked after the stream
* operations are completed

对我来说,“及时处置”听起来仍然像是资源最终会在应用程序退出之前发布。我查看了JDK(1.8.60)代码,但我无法找到有关再次释放时打开的文件句柄的任何提示。Files.list

然后,我创建了一个小应用程序,在使用后显式调用垃圾回收器,如下所示:Files.list

while (true) {
    Files.list(Paths.get("/")).forEach(path -> {
      System.out.println(path);
    });
    Thread.sleep(5000);

    System.gc();
    System.runFinalization();
}

当我检查打开的文件句柄时,我仍然可以看到“/”的打开文件句柄列表越来越长。lsof -p <pid>

我现在的问题是:在这种情况下,是否有任何隐藏的机制最终应该关闭不再使用的打开文件句柄?还是这些资源实际上从未被处置过,javadoc在谈论“及时处置文件系统资源”时有点委婉?


答案 1

如果关闭 Stream,则确实会关闭它用于流式传输文件的基础,因此只要关闭 Stream,就不会有资源泄漏。Files.list()DirectoryStream

您可以在源代码中看到 关闭位置:DirectoryStreamFiles.list()

return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false)
                    .onClose(asUncheckedRunnable(ds));

要了解的关键是,a 已注册到流,使用在流本身关闭时调用。该 Runnable 由工厂方法创建,该方法创建一个关闭传递给它的资源,将 期间抛出的任何内容转换为RunnableStream::onCloseasUncheckedRunnableRunnableIOExceptionclose()UncheckedIOException

您可以通过确保 关闭,如下所示,可以安全地确保 关闭:DirectoryStreamStream

try (Stream<Path> files = Files.list(Paths.get(destination))){
    files.forEach(path -> {
         // Do stuff
    });
}

答案 2

关于IDE部分:Eclipse基于局部变量(和显式资源分配表达式)执行资源泄漏分析,因此您只需要将流提取到局部变量:

Stream<Path> files =Files.list(Paths.get(destination));
files.forEach(path -> {
 // To stuff
});

然后Eclipse会告诉你

资源泄漏:“文件”从不关闭

在后台,分析工作有一连串的例外情况:

  1. 所有 s 都需要关闭Closeable
  2. java.util.stream.Stream(这是可关闭的)不需要关闭
  3. 中的方法生成的所有流都需要关闭java.nio.file.Files

该策略是与图书馆团队协调制定的,当时他们讨论了是否应该 。StreamAutoCloseable


推荐