J'ai récemment écrit une petite application qui vérifiait périodiquement le contenu d'un répertoire. Après un certain temps, l'application est tombée en panne à cause d'un trop grand nombre de descripteurs de fichiers ouverts. Après un certain débogage, j'ai trouvé l'erreur dans la ligne suivante:
Files.list(Paths.get(destination)).forEach(path -> {
// To stuff
});
J'ai ensuite vérifié le javadoc (j'aurais probablement dû le faire plus tôt) pour Files.list
et trouvé:
* <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
Pour moi, "l'élimination rapide" semble encore indiquer que les ressources vont être libérées, avant la fermeture de l'application. J'ai parcouru le code JDK (1.8.60), mais je n'ai trouvé aucun indice sur les descripteurs de fichiers ouverts par Files.list
étant publiés à nouveau.
J'ai ensuite créé une petite application qui appelle explicitement le ramasse-miettes après avoir utilisé Files.list
comme ceci:
while (true) {
Files.list(Paths.get("/")).forEach(path -> {
System.out.println(path);
});
Thread.sleep(5000);
System.gc();
System.runFinalization();
}
Quand j'ai vérifié les descripteurs de fichiers ouverts avec lsof -p <pid>
, je pouvais toujours voir la liste des descripteurs de fichiers ouverts pour "/" s'allonger.
Ma question est la suivante: existe-t-il un mécanisme caché qui devrait éventuellement fermer les descripteurs de fichiers ouverts non utilisés dans ce scénario? Ou bien ces ressources ne sont-elles jamais réellement disposées et le javadoc est un peu euphémique lorsqu'il est question de "l'élimination rapide des ressources du système de fichiers"?
Si vous fermez le flux, Files.list()
ferme la DirectoryStream
sous-jacente qu'il utilise pour diffuser les fichiers. Par conséquent, il ne devrait y avoir aucune fuite de ressource tant que vous fermez le flux.
Vous pouvez voir où la DirectoryStream
est fermée dans le code source de Files.list()
ici:
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false)
.onClose(asUncheckedRunnable(ds));
Il est important de comprendre que Runnable
est enregistré auprès du flux à l'aide de Stream::onClose
appelé lorsque le flux lui-même est fermé. This Runnable est créé par une méthode fabrique, asUncheckedRunnable
, qui crée une Runnable
qui ferme la ressource qui y est passée, en traduisant toute IOException
émise lors de la close()
en une UncheckedIOException
.
Vous pouvez vous assurer en toute sécurité que la DirectoryStream
est fermée en veillant à ce que Stream
soit fermé comme suit:
try (Stream<Path> files = Files.list(Paths.get(destination))){
files.forEach(path -> {
// Do stuff
});
}
En ce qui concerne la partie IDE: Eclipse effectue une analyse de fuite de ressources en fonction de variables locales (et d'expressions d'allocation de ressources explicites). Il vous suffit donc d'extraire le flux dans une variable locale:
Stream<Path> files =Files.list(Paths.get(destination));
files.forEach(path -> {
// To stuff
});
Ensuite, Eclipse vous dira
Fuite de ressources: les «fichiers» ne sont jamais fermés
En coulisse, l'analyse fonctionne avec une cascade d'exceptions:
Closeable
s doivent être fermésJava.util.stream.Stream
(qui est Closeable) fait pas besoin de fermerJava.nio.file.Files
do doivent être fermésCette stratégie a été élaborée en coordination avec l’équipe de la bibliothèque qui a demandé si Stream
devrait être AutoCloseable
.
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);
}