Un guide pour finaliser la méthode en Java

Un guide pour finaliser la méthode en Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons nous concentrer sur un aspect fondamental du langage Java - la méthodefinalize fournie par la classe racineObject.

En termes simples, cela s'appelle avant la récupération de place pour un objet particulier.

2. Utilisation des finaliseurs

La méthodefinalize() est appelée le finaliseur.

Les finaliseurs sont appelés lorsque la machine virtuelle Java détermine que cette instance particulière doit être nettoyée. Un tel finaliseur peut effectuer toutes les opérations, y compris ramener l'objet à la vie.

L’objectif principal d’un finaliseur est cependant de libérer les ressources utilisées par les objets avant qu’ils ne soient supprimés de la mémoire. Un finaliseur peut fonctionner comme mécanisme principal pour les opérations de nettoyage ou comme filet de sécurité en cas d'échec des autres méthodes.

Pour comprendre le fonctionnement d'un finaliseur, examinons une déclaration de classe:

public class Finalizable {
    private BufferedReader reader;

    public Finalizable() {
        InputStream input = this.getClass()
          .getClassLoader()
          .getResourceAsStream("file.txt");
        this.reader = new BufferedReader(new InputStreamReader(input));
    }

    public String readFirstLine() throws IOException {
        String firstLine = reader.readLine();
        return firstLine;
    }

    // other class members
}

La classeFinalizable a un champreader, qui fait référence à une ressource fermable. Lorsqu'un objet est créé à partir de cette classe, il construit une nouvelle instanceBufferedReader en lisant un fichier dans le chemin de classe.

Une telle instance est utilisée dans la méthodereadFirstLine pour extraire la première ligne du fichier donné. Notice that the reader isn’t closed in the given code.

Nous pouvons le faire en utilisant un finaliseur:

@Override
public void finalize() {
    try {
        reader.close();
        System.out.println("Closed BufferedReader in the finalizer");
    } catch (IOException e) {
        // ...
    }
}

Il est facile de voir qu’un finaliseur est déclaré comme n’importe quelle méthode d’instance normale.

En réalité,the time at which the garbage collector calls finalizers is dependent on the JVM’s implementation and the system’s conditions, which are out of our control.

Pour que le garbage collection se produise sur place, nous allons tirer parti de la méthodeSystem.gc. Dans les systèmes du monde réel, nous ne devrions jamais invoquer cela explicitement, pour un certain nombre de raisons:

  1. C’est coûteux

  2. Il ne déclenche pas le garbage collection immédiatement - il s’agit simplement d’un indice pour que la machine virtuelle Java démarre GC

  3. JVM sait mieux quand le GC doit être appelé

Si nous devons forcer GC, nous pouvons utiliserjconsole pour cela.

Ce qui suit est un scénario de test démontrant le fonctionnement d'un finaliseur:

@Test
public void whenGC_thenFinalizerExecuted() throws IOException {
    String firstLine = new Finalizable().readFirstLine();
    assertEquals("example.com", firstLine);
    System.gc();
}

Dans la première instruction, un objetFinalizable est créé, puis sa méthodereadFirstLine est appelée. Cet objet n’est affecté à aucune variable, il est donc éligible pour le garbage collection lorsque la méthodeSystem.gc est appelée.

L'assertion dans le test vérifie le contenu du fichier d'entrée et sert uniquement à prouver que notre classe personnalisée fonctionne comme prévu.

Lorsque nous exécutons le test fourni, un message indiquant que le lecteur en mémoire tampon est fermé dans le finaliseur sera imprimé. Cela implique que la méthodefinalize a été appelée et qu'elle a nettoyé la ressource.

Jusqu'à présent, les finaliseurs semblent être un excellent moyen de mener des opérations de pré-destruction. Cependant, ce n’est pas tout à fait vrai.

Dans la section suivante, nous verrons pourquoi leur utilisation doit être évitée.

3. Éviter les finaliseurs

Jetons un coup d'œil à plusieurs problèmes auxquels nous serons confrontés lors de l'utilisation des finaliseurs pour effectuer des actions critiques.

Le premier problème notable associé aux finaliseurs est le manque de rapidité. Nous ne pouvons pas savoir quand un finaliseur est exécuté car la récupération de place peut avoir lieu à tout moment.

En soi, ce n’est pas un problème car le plus important est que le finaliseur soit toujours appelé, tôt ou tard. Cependant, les ressources système sont limitées. Thus, we may run out of those resources before they get a chance to be cleaned up, potentially resulting in system crashes.

Les finaliseurs ont également un impact sur la portabilité du programme. Comme l'algorithme de récupération de place dépend de la mise en œuvre de la machine virtuelle Java, un programme peut très bien s'exécuter sur un système tout en se comportant différemment à l'exécution sur un autre.

Un autre problème important lié aux finaliseurs est le coût de la performance. Plus précisément,JVM must perform much more operations when constructing and destroying objects containing a non-empty finalizer.

Les détails sont spécifiques à l'implémentation, mais les idées générales sont les mêmes pour toutes les machines virtuelles: des étapes supplémentaires doivent être suivies pour s'assurer que les finaliseurs sont exécutés avant que les objets ne soient ignorés. Ces étapes peuvent augmenter de plusieurs centaines, voire de milliers de fois la durée de la création et de la destruction des objets.

Le dernier problème dont nous allons parler est le manque de gestion des exceptions lors de la finalisation. If a finalizer throws an exception, the finalization process is canceled, and the exception is ignored, leaving the object in a corrupted state sans aucune notification.

4. Exemple sans finaliseur

Explorons une solution offrant la même fonctionnalité mais sans utiliser la méthodefinalize(). Notez que l'exemple ci-dessous n'est pas le seul moyen de remplacer les finaliseurs.

Au lieu de cela, il est utilisé pour démontrer un point important: il existe toujours des options qui nous aident à éviter les finaliseurs.

Voici la déclaration de notre nouvelle classe:

public class CloseableResource implements AutoCloseable {
    private BufferedReader reader;

    public CloseableResource() {
        InputStream input = this.getClass()
          .getClassLoader()
          .getResourceAsStream("file.txt");
        reader = new BufferedReader(new InputStreamReader(input));
    }

    public String readFirstLine() throws IOException {
        String firstLine = reader.readLine();
        return firstLine;
    }

    @Override
    public void close() {
        try {
            reader.close();
            System.out.println("Closed BufferedReader in the close method");
        } catch (IOException e) {
            // handle exception
        }
    }
}

Il n’est pas difficile de voir que la seule différence entre la nouvelle classeCloseableResource et notre précédente classeFinalizable est l’implémentation de l’interfaceAutoCloseable au lieu d’une définition de finaliseur.

Notez que le corps de la méthodeclose deCloseableResource est presque le même que le corps du finaliseur de la classeFinalizable.

Voici une méthode de test qui lit un fichier d'entrée et libère la ressource une fois son travail terminé:

@Test
public void whenTryWResourcesExits_thenResourceClosed() throws IOException {
    try (CloseableResource resource = new CloseableResource()) {
        String firstLine = resource.readFirstLine();
        assertEquals("example.com", firstLine);
    }
}

Dans le test ci-dessus, une instanceCloseableResource est créée dans le bloctry d'une instruction try-with-resources, par conséquent cette ressource est automatiquement fermée lorsque le bloc try-with-resources termine l'exécution.

En exécutant la méthode de test donnée, nous verrons un message imprimé à partir de la méthodeclose de la classeCloseableResource.

5. Conclusion

Dans ce didacticiel, nous nous sommes concentrés sur un concept de base en Java - la méthodefinalize. Cela semble utile sur papier mais peut avoir des effets secondaires déplaisants au moment de l'exécution. Et, plus important encore, il existe toujours une solution alternative à l’utilisation d’un finaliseur.

Un point critique à noter est quefinalize est obsolète à partir de Java 9 - et sera finalement supprimé.

Comme toujours, le code source de ce tutoriel peut être trouvéover on GitHub.