Утечка ресурсов в Files.list(путь), когда поток явно не закрыт?
Недавно я написал небольшое приложение, которое периодически проверяло содержимое каталога. Через некоторое время приложение разбилось из-за слишком большого количества открытых файлов. После некоторой отладки я обнаружил ошибку в следующей строке:
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 {@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 немного эвфемистичен, когда говорит о "своевременной утилизации ресурсов файловой системы"?
Ответы
Ответ 1
Если вы закрываете поток, Files.list()
закрывает базовый DirectoryStream
, который он использует для потоковой передачи файлов, поэтому утечка ресурсов не должна протекать, пока вы закрываете поток.
Вы можете видеть, где DirectoryStream
закрыт в исходном коде для Files.list()
здесь:
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false)
.onClose(asUncheckedRunnable(ds));
Главное, чтобы понять, что Runnable
зарегистрирован в потоке с использованием Stream::onClose
, который вызывается, когда сам поток закрыт. Этот Runnable создается с помощью метода factory, asUncheckedRunnable
, который создает Runnable
, который закрывает переданный в него ресурс, переводя любой IOException
, созданный во время close()
, на UncheckedIOException
Вы можете с уверенностью уверять, что DirectoryStream
закрыт, закрыв Stream
следующим образом:
try (Stream<Path> files = Files.list(Paths.get(destination))){
files.forEach(path -> {
// Do stuff
});
}
Ответ 2
Что касается части IDE: Eclipse выполняет анализ утечки ресурсов на основе локальных переменных (и явных выражений выделения ресурсов), поэтому вам нужно только извлечь поток в локальную переменную:
Stream<Path> files =Files.list(Paths.get(destination));
files.forEach(path -> {
// To stuff
});
Затем Eclipse расскажет вам
утечка ресурса: "файлы" никогда не закрываются
За кулисами анализ работает с каскадом исключений:
- Все
Closeable
необходимо закрыть
-
java.util.stream.Stream
(который является Closeable) закрывает не
- Все потоки, создаваемые методами
java.nio.file.Files
do, нуждаются в закрытии
Эта стратегия была разработана в координации с библиотечной командой, когда они обсуждали, должно ли 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);
}