我最近写了一个小应用程序,定期检查目录的内容。一段时间后,由于打开的文件句柄过多,应用程序崩溃了。经过一些调试,我在以下行中发现了错误:
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会告诉你
资源泄漏:"文件"永不关闭
在幕后,分析处理了一系列异常:
- 所有
Closeable
都需要关闭 -
java.util.stream.Stream
(可关闭(不需要关闭 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);
}