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

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

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

然后我检查了文件列表中的javadoc(我可能应该早些做过)并找到:

 * 

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 检查打开的文件句柄时,我仍然可以看到“/”的打开文件句柄列表变得越来越长。

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

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

您可以在这里的Files.list()的源代码中看到DirectoryStream的关闭位置:

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

要理解的关键是使用Stream::onClose向Stream注册Runnable ,当流本身关闭时调用它。 Runnable由工厂方法创建, asUncheckedRunnable创建一个Runnable ,用于关闭传递给它的资源,将close()期间抛出的任何IOException转换为UncheckedIOException

您可以通过确保Stream关闭来确保关闭DirectoryStream

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

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

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

然后Eclipse将告诉你

资源泄漏:“文件”永远不会关闭

在幕后,分析使用一系列例外:

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

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

  List fileList = null; try (Stream list = Files.list(Paths.get(path.toString()))) { fileList = list.filter(Files::isRegularFile).map(Path::toFile).map(File::getAbsolutePath) .collect(Collectors.toList()); } catch (IOException e) { logger.error("Error occurred while reading email files: ", e); }