【问题标题】:Resource leak in Files.list(Path dir) when stream is not explicitly closed?当流未明确关闭时,Files.list(Path dir) 中的资源泄漏?
【发布时间】:2016-08-27 16:01:18
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 你是对的,如果你不关闭流,那么文件句柄将永远不会被关闭。
  • 但是,如果您的进程退出,操作系统将在它之后进行清理。我怀疑“及时”是指“在流程结束之前”
  • 可能是这样。尽管我发现您不应该像那样使用流并不是很直观。此外,IDE 似乎不会警告您这种潜在的资源泄漏,即使它应该很容易找到。
  • IDE 不会发出警告,因为几乎所有 Streams 都不需要关闭。理想情况下,Files.list() 应该有一些 IDE 可以理解的注释,例如 @CloseTheReturnedStream
  • 您的测试代码假定必须通过finalize() 执行自动清理,但通常现代代码使用基于PhantomReference 的清理程序,这些清理程序不是由System.runFinalization() 触发的。所以即使有自动清理,你也不会用你的代码检测到它。但是看the implementationits creator 你不会看到清洁工……

标签: java stream java-8


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 你拯救了我的一天!
  • 我们在这段代码中究竟在哪里关闭流?
  • @Simrankaur,这是 try_with_resources 的一个例子。 try之后括号内声明的变量会自动关闭,不管try块是正常完成还是异常完成。
  • 太好了,谢谢。
【解决方案2】:

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

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

然后Eclipse会告诉你

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

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

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

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

【讨论】:

    【解决方案3】:
       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);
      }
    

    【讨论】:

    • 虽然这段代码可以回答这个问题,但最好包含一些上下文,解释它是如何工作的以及何时使用它。从长远来看,纯代码的答案没有用处。
    猜你喜欢
    • 2016-06-06
    • 1970-01-01
    • 2020-07-30
    • 1970-01-01
    • 2012-09-13
    • 2014-06-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多