当流未显式关闭时,文件列表(路径目录)中的资源泄漏



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

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 在谈论"及时处置文件系统资源"时有点委婉?

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

您可以在此处查看源代码中DirectoryStream关闭的位置Files.list()

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

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

您可以通过确保Stream像这样关闭来安全地确保DirectoryStream已关闭:

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

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

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

然后Eclipse会告诉你

资源泄漏:"文件"永不关闭

在幕后,分析处理了一系列异常:

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

该策略是在与图书馆团队讨论是否应AutoCloseable Stream时协调制定的。

   List<String> fileList = null;
   try (Stream<Path> 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);
  }

最新更新