Pourquoi la méthode finalize est-elle incluse dans Java?

44

Selon ce billet , nous ne devrions jamais nous fier à la méthode finalize pour être appelée. Alors, pourquoi Java l'a-t-il inclus dans le langage de programmation?

Cela semble être une décision terrible d’inclure dans un langage de programmation une fonction qui pourrait être appelée.

patstuart
la source

Réponses:

41

Selon Effective Java (Deuxième édition) de Joshua Bloch, deux scénarios finalize()sont utiles lorsque :

  1. L'une consiste à agir en tant que «filet de sécurité» au cas où le propriétaire d'un objet oublie d'appeler sa méthode de terminaison explicite. Bien que rien ne garantisse que le finaliseur sera appelé rapidement, il peut être préférable de libérer la ressource plus tard que jamais, dans les cas (espérons-le rares) où le client ne parvient pas à appeler la méthode de terminaison explicite. Mais le finaliseur doit enregistrer un avertissement s’il constate que la ressource n’a pas été arrêtée.

  2. Une deuxième utilisation légitime des finaliseurs concerne les objets avec des pairs natifs. Un homologue natif est un objet natif auquel un objet normal délègue via des méthodes natives. Etant donné qu'un homologue natif n'est pas un objet normal, le récupérateur de mémoire ne le connaît pas et ne peut pas le récupérer lorsque son homologue Java est récupéré. Un finaliseur est un véhicule approprié pour effectuer cette tâche, en supposant que l'homologue natif ne possède aucune ressource critique. Si le pair natif contient des ressources qui doivent être terminées rapidement, la classe doit avoir une méthode de terminaison explicite, comme décrit ci-dessus. La méthode de terminaison doit faire le nécessaire pour libérer la ressource critique.

Pour en savoir plus, reportez-vous à la rubrique 7, page 27.

bbalchev
la source
4
Avec # 2, l’homologue natif est une ressource native qui requiert un filet de sécurité et devrait avoir une méthode de terminaison, et ne devrait donc pas être un point séparé.
Mooing Duck
2
@MooingDuck: # 2 est un point distinct car, si l'homologue natif ne contient pas de ressources critiques (par exemple, il s'agit simplement d'un objet en mémoire), il n'est pas nécessaire d'exposer une méthode de terminaison explicite. Le filet de sécurité de # 1 concerne explicitement l’utilisation d’une méthode de terminaison.
Jhominal
55

Les finaliseurs sont importants pour la gestion des ressources natives. Par exemple, votre objet peut avoir besoin d'allouer un WidgetHandle à partir du système d'exploitation à l'aide d'une API non Java. Si vous ne relâchez pas ce WidgetHandle lorsque votre objet est défini sur GC'd, vous allez laisser filtrer WidgetHandles.

Ce qui est important, c'est que le cas "du finaliseur ne s'appelle jamais" se décompose plutôt simplement:

  1. Le programme s'arrête rapidement
  2. L'objet "vit pour toujours" pendant la durée du programme
  3. L'ordinateur s'éteint / votre processus est tué par le système d'exploitation / etc

Dans ces trois cas, vous n’avez pas de fuite native (du fait que votre programme ne fonctionne plus), ou vous avez déjà une fuite non native (si vous continuez à allouer des objets gérés sans les GC'd).

L'avertissement "ne comptez pas sur le finaliseur étant appelé" est en réalité sur le fait de ne pas utiliser les finaliseurs pour la logique de programme. Par exemple, vous ne voulez pas savoir combien de vos objets existent dans toutes les instances de votre programme en incrémentant un compteur dans un fichier quelque part pendant la construction et en le décrémentant dans un finaliseur - car rien ne garantit que vos objets être finalisé, ce compteur de fichiers ne reviendra probablement jamais à 0. C’est là un cas particulier du principe plus général qui veut que vous ne dépendiez pas de la fin normale de votre programme (pannes de courant, etc.).

Pour la gestion des ressources natives, cependant, les cas où le finaliseur ne s'exécute pas correspondent aux cas où vous ne vous souciez pas de son exécution.

Ryan Cavanaugh
la source
Excellente réponse. J'étais comme même si ça pouvait s'appeler… ça devrait quand même être inclus. J'ai été confus pendant un moment avec la question et celle liée sur StackOverflow.
InformedA
3
Ce n'était pas dans la question, mais cela vaut la peine de discuter dans le contexte de "ne comptez pas sur le finaliseur appelé": le finaliseur peut être appelé, mais seulement après un délai arbitrairement long après que l'objet soit devenu inaccessible. Ceci est important pour les "ressources natives" qui sont beaucoup plus rares que la mémoire (qui sont la plupart d’entre elles).
26
"Les finaliseurs sont importants pour la gestion des ressources natives." - nooooooooo, désolé. Utiliser les finaliseurs pour gérer les ressources natives est une idée horrible (c'est pourquoi nous avons maintenant autoclosable et co). Le GC ne s'intéresse qu'à la mémoire, pas à aucune autre ressource. Cela signifie que vous pouvez manquer de ressources natives tout en ayant suffisamment de mémoire pour ne pas déclencher un GC. Nous ne le souhaitons vraiment pas. De plus, les finaliseurs rendent le GC plus cher, ce qui n’est pas une bonne idée non plus.
Voo le
12
Je suis d' accord que vous devriez toujours avoir une stratégie explicite de gestion des ressources maternelle autre que finaliseurs, mais si votre objet ne les affecter et ne les libère pas dans le finaliseur, vous vous exposez à une fuite d' origine dans le cas où l'objet est GC'd sans la libération explicite de la ressource. En d' autres termes, finaliseurs sont une importante partie d'une stratégie de gestion des ressources indigènes, pas une solution au problème en général.
Ryan Cavanaugh
1
@Voo, c'est probablement plus correct de dire que le finaliseur est la solution de rechange au cas où le contrat de l'objet ne serait pas suivi ( close()n'est jamais appelé avant qu'il ne soit inaccessible)
phénomène de cliquet
5

Le but de cette méthode est expliqué dans la documentation de l'API comme suit:

il est appelé si et quand la machine virtuelle Java a déterminé qu'il n'y a plus aucun moyen d'accéder à cet objet par un thread qui n'est pas encore mort, sauf à la suite d'une action entreprise par la finalisation d'un autre objet. ou classe qui est prêt à être finalisé ...

Le but habituel de finalize... est d' effectuer des opérations de nettoyage avant que l'objet ne soit irrévocablement jeté . Par exemple, la méthode finalize d'un objet qui représente une connexion d'entrée / sortie peut effectuer des transactions d'E / S explicites afin de rompre la connexion avant que l'objet ne soit définitivement ignoré ...


Si vous êtes également intéressé par les raisons pour lesquelles les concepteurs de langage ont choisi cet "objet est irrévocablement jeté" ( ordures collectées ) d'une manière qui échappe au contrôle du programmeur d'application ("nous ne devrions jamais nous fier"), cela a été expliqué dans une réponse à question :

le ramassage automatique des ordures ... élimine des classes entières d'erreurs de programmation qui empêchent les programmeurs C et C ++. Vous pouvez développer du code Java en sachant que le système trouvera rapidement de nombreuses erreurs et que les problèmes majeurs ne resteront pas en sommeil tant que votre code de production n'aura pas été expédié.

La citation ci-dessus, à son tour, a été extraite de la documentation officielle sur les objectifs de conception Java , c’est-à-dire qu’elle peut être considérée comme une référence faisant autorité qui explique pourquoi les concepteurs de langage Java ont pris cette décision.

Pour une discussion plus détaillée et agnostique de la langue de cette préférence, référez- vous à la section 9.6 de l' OOSC Gestion automatique de la mémoire (en fait, non seulement cette section, mais tout le chapitre 9 vaut vraiment la peine d'être lu si vous vous intéressez à ce genre de choses). Cette section s'ouvre par une déclaration sans ambiguïté:

Un bon environnement OO devrait offrir un mécanisme de gestion automatique de la mémoire qui détectera et récupérera les objets inaccessibles, permettant ainsi aux développeurs d'applications de se concentrer sur le développement de leurs tâches.

La discussion qui précède devrait suffire à montrer combien il est important de disposer d’une telle installation. Dans les mots de Michael Schweitzer et Lambert Strether:

Un programme orienté objet sans gestion automatique de la mémoire est à peu près identique à un autocuiseur sans soupape de sécurité: tôt ou tard, tout va bien exploser!

moucheron
la source
4

Les finaliseurs existent parce qu’ils étaient censés être un moyen efficace d’assurer le nettoyage des objets (même s’ils ne le sont pas en pratique), et parce que, lorsqu’ils ont été inventés, de meilleurs moyens d’assurer le nettoyage (tels que les références fantômes et les tests d’essai). ressources) n’existait pas encore. Avec le recul, Java serait probablement mieux si les efforts consacrés à la mise en œuvre de sa fonctionnalité "finalize" avaient été consacrés à d'autres moyens de nettoyage, mais cela n'était guère clair au moment du développement initial de Java.

supercat
la source
3

CAVEAT: Je suis peut-être obsolète, mais c'est ce que j'avais compris il y a quelques années:

En général, rien ne garantit le moment où un finaliseur est exécuté - ou même son exécution, bien que certaines machines virtuelles vous permettent de demander un GC complet et la finalisation avant la fin du programme (ce qui signifie bien sûr que le programme prend plus de temps. quitter, et qui n’est pas le mode de fonctionnement par défaut).

Et certains GC étaient connus pour retarder ou éviter explicitement les objets GC ayant des finaliseurs, dans l’espoir que cela produirait de meilleures performances sur les points de repère.

Ces comportements sont malheureusement en conflit avec les raisons initiales recommandées par les finaliseurs et ont plutôt encouragé l'utilisation de méthodes d'arrêt explicitement appelées.

Si vous avez vraiment un objet à nettoyer avant de le jeter, et si vous ne pouvez vraiment pas faire confiance aux utilisateurs, un finaliseur peut toujours être utile. Mais en règle générale, il existe de bonnes raisons pour lesquelles vous ne les voyez pas aussi souvent dans le code Java moderne que dans certains des premiers exemples.

Keshlam
la source
1

Le Guide de style Java de Google contient quelques conseils avisés sur le sujet:

Il est extrêmement rare de passer outre Object.finalize.

Astuce : ne le fais pas. Si vous devez absolument, commencez par lire et comprendre très attentivement le chapitre 7 de Effective Java , "Evitez les finaliseurs", puis ne le faites pas.

Daniel Pryden
la source
5
Pourquoi j'admire la simplification excessive du problème, "ne le faites pas" n'est pas une réponse à "pourquoi existe-t-il".
Pierre Arlaud
1
Je suppose que je n’ai pas bien précisé ma réponse, mais (IMO) répond "pourquoi existe-t-il?" est "il ne devrait pas". Dans les rares cas où vous avez vraiment besoin d'une opération de finalisation, vous souhaiterez probablement la construire vous-même avec PhantomReferenceet à la ReferenceQueueplace.
Daniel Pryden
Je voulais dire alors * évidemment, bien que je ne vous ai pas voté. La réponse la plus votée montre cependant à quel point la méthode est utile, ce qui invalide un peu votre argument.
Pierre Arlaud le
1
Même dans le cas des pairs natifs, je pense que PhantomReferencec'est une meilleure solution. Les finaliseurs sont une verrue des débuts de Java et, comme Object.clone()les types bruts, font partie du langage le mieux oublié.
Daniel Pryden
1
Les dangers des finaliseurs sont décrits de manière plus accessible (compréhensible par les développeurs) en C #. Cependant, il ne faut pas sous- entendre que les mécanismes sous-jacents sous Java et C # sont identiques. rien dans leurs spécifications ne le dit.
Rwong
1

La spécification du langage Java (Java SE 7) indique:

Les finaliseurs permettent de libérer des ressources qui ne peuvent pas être libérées automatiquement par un gestionnaire de stockage automatique. Dans de telles situations, le simple fait de récupérer la mémoire utilisée par un objet ne garantirait pas que les ressources qu'il détiendrait seraient récupérées.

Benni
la source