当流未明确关闭时,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将告诉你
资源泄漏:“文件”永远不会关闭
在幕后,分析使用一系列例外:
- 所有
Closeable
需要关闭 -
java.util.stream.Stream
(可以关闭)不需要关闭 - 由
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); }