web-dev-qa-db-fra.com

Fuite de ressources dans Files.list (répertoire de chemin) lorsque le flux n'est pas explicitement fermé?

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"?

17
Jan Gassen

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
    });
}
17
Hank D

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:

  1. Tous les Closeables doivent être fermés
  2. Java.util.stream.Stream (qui est Closeable) fait pas besoin de fermer
  3. Tous les flux produits par les méthodes de Java.nio.file.Files do doivent être fermés

Cette stratégie a été élaborée en coordination avec l’équipe de la bibliothèque qui a demandé si Stream devrait être AutoCloseable.

2
   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);
  }
0
Sudhir