java - parallel - ¿Fuga de recursos en Files.list(ruta dir) cuando la secuencia no está cerrada explícitamente?
stream parallel java 8 (3)
Con respecto a la parte IDE: Eclipse realiza un análisis de fugas de recursos basado en variables locales (y expresiones de asignación de recursos explícitas), por lo que solo tiene que extraer el flujo a una variable local:
Stream<Path> files =Files.list(Paths.get(destination));
files.forEach(path -> {
// To stuff
});
Entonces Eclipse te dirá
Fuga de recursos: ''archivos'' nunca se cierran
Detrás de escena, el análisis funciona con una cascada de excepciones:
- Todo
Closeable
s necesita cierre -
java.util.stream.Stream
(que se puede cerrar) no necesita cerrarse - Todas las secuencias producidas por métodos en
java.nio.file.Files
necesitan cerrarse
Esta estrategia se desarrolló en coordinación con el equipo de la biblioteca cuando discutieron si Stream
debería ser AutoCloseable
o no.
Recientemente escribí una pequeña aplicación que verificaba periódicamente el contenido de un directorio. Después de un tiempo, la aplicación se bloqueó debido a demasiados manejadores de archivos abiertos. Después de un poco de depuración, encontré el error en la siguiente línea:
Files.list(Paths.get(destination)).forEach(path -> {
// To stuff
});
Luego revisé el javadoc (probablemente debería haberlo hecho antes) para Files.list
y encontré:
* <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
Para mí, la "eliminación oportuna" todavía parece que los recursos se liberarán eventualmente, antes de que la aplicación se cierre. Revisé el código JDK (1.8.60) pero no pude encontrar ninguna pista sobre los manejadores de archivos abiertos por Files.list
se lanzaron nuevamente.
Luego creé una pequeña aplicación que llama explícitamente al recolector de basura después de usar Files.list
esta forma:
while (true) {
Files.list(Paths.get("/")).forEach(path -> {
System.out.println(path);
});
Thread.sleep(5000);
System.gc();
System.runFinalization();
}
Cuando verifiqué los manejadores de archivos abiertos con lsof -p <pid>
todavía podía ver la lista de manejadores de archivos abiertos para que "/" se alargara más y más.
Mi pregunta ahora es: ¿hay algún mecanismo oculto que eventualmente deba cerrarse y ya no se utilicen los manejadores de archivos abiertos en este escenario? ¿O estos recursos, de hecho, nunca se disponen y el javadoc es un poco eufemístico cuando se habla de "eliminación oportuna de los recursos del sistema de archivos"?
Si cierra la secuencia, Files.list()
sí cierra el DirectoryStream
subyacente que utiliza para transmitir los archivos, por lo que no debería haber pérdida de recursos siempre que cierre la secuencia.
Puede ver dónde se cierra DirectoryStream
en el código fuente de Files.list()
aquí:
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false)
.onClose(asUncheckedRunnable(ds));
La clave a entender es que un Runnable
se registra con Stream usando Stream::onClose
que se llama cuando se cierra el stream. Ese Runnable es creado por un método de fábrica, como asUncheckedRunnable
que crea un Runnable
que cierra el recurso pasado a él, traduciendo cualquier IOException
lanzada durante el close()
a un UncheckedIOException
Puede asegurarse con seguridad de que DirectoryStream
está cerrado asegurándose de que Stream
se cierre de esta manera:
try (Stream<Path> files = Files.list(Paths.get(destination))){
files.forEach(path -> {
// Do stuff
});
}
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);
}