Les deux principaux arguments contre le dépassement Object.finalize()
sont les suivants:
Vous ne décidez pas quand ça s'appelle.
Il peut ne pas être appelé du tout.
Si je comprends bien, je ne pense pas que ce soient des raisons suffisantes pour haïr Object.finalize()
autant.
Il incombe à l'implémentation de la VM et au GC de déterminer le moment propice pour libérer un objet, et non le développeur. Pourquoi est-il important de décider quand
Object.finalize()
se faire appeler?Normalement, et corrigez-moi si je me trompe, le seul moment où
Object.finalize()
on ne m'appelle pas, c'est quand l'application est terminée avant que le GC ne puisse s'exécuter. Cependant, l'objet a été désalloué de toute façon lorsque le processus de l'application a été interrompu. Donc,Object.finalize()
ne pas être appelé parce qu'il n'était pas nécessaire d'appeler. Pourquoi le développeur s'en préoccuperait-il?
Chaque fois que j'utilise des objets que je dois fermer manuellement (comme des descripteurs de fichiers et des connexions), je suis très frustré. Je dois vérifier en permanence si un objet a une implémentation close()
, et je suis certain d'avoir manqué quelques appels à ce sujet par le passé. Pourquoi n'est-il pas simplement plus simple et plus sûr de laisser le soin à la VM et au GC de se débarrasser de ces objets en intégrant la close()
mise en œuvre Object.finalize()
?
la source
finalize()
est un peu gâchée. Si vous l'implémentez, assurez-vous qu'il est thread-safe par rapport à toutes les autres méthodes sur le même objet.Réponses:
D'après mon expérience, il n'y a qu'une et une seule raison de ne pas le faire
Object.finalize()
, mais c'est une très bonne raison :L'analyse statique ne peut détecter que les omissions dans les scénarios d'utilisation triviaux, et les avertissements du compilateur mentionnés dans une autre réponse ont une vue tellement simpliste de la nécessité de les désactiver pour pouvoir effectuer quelque chose de non trivial. (J'ai beaucoup plus d'avertissements activés que n'importe quel autre programmeur que je connaisse ou dont j'ai entendu parler, mais je n'ai pas activé les avertissements stupides.)
La finalisation peut sembler être un bon moyen de s’assurer que les ressources ne restent pas inaperçues, mais la plupart des gens le voient d’une manière totalement fausse: ils le voient comme un mécanisme de secours alternatif, une sauvegarde "de la seconde chance" qui sauvera automatiquement le jour en disposant des ressources qu’ils ont oubliées. C'est complètement faux . Il doit exister une seule façon de faire une chose: soit on ferme toujours tout, soit la finalisation ferme toujours tout. Mais comme la finalisation n'est pas fiable, elle ne peut l'être.
Donc, il y a ce schéma que j'appelle élimination obligatoire , et il stipule que le programmeur est responsable de toujours fermer explicitement tout ce qui implémente
Closeable
ouAutoCloseable
. (L'instruction try-with-resources compte toujours en tant que clôture explicite.) Bien sûr, le programmeur peut oublier. C'est là que la finalisation entre en jeu, mais pas en tant que fée magique qui remettra les choses à la magie à la fin: si la finalisation découvre quiclose()
n'a pas été invoqué, cela ne signifie pasessayez de l'invoquer, précisément parce qu'il y aura (avec une certitude mathématique) des hordes de programmeurs n00b qui compteront dessus pour faire le travail pour lequel ils étaient trop paresseux ou trop distants. Donc, avec l'élimination obligatoire, lorsque la finalisation découvre que celaclose()
n'a pas été invoqué, un message d'erreur rouge vif est consigné, indiquant au programmeur, avec de grosses lettres majuscules, de réparer ses affaires.Un avantage supplémentaire, selon la rumeur , "la machine virtuelle Java ignorera une méthode trivial finalize () (par exemple, une méthode qui retourne sans rien faire, comme celle définie dans la classe Object)", ce qui permet d' éviter toute suppression à la finalisation. des frais généraux dans tout votre système ( voir la réponse d' Alip pour des informations sur la gravité de ces frais) en codant votre
finalize()
méthode comme suit :L’idée sous-jacente est qu’il s’agisse d’
Global.DEBUG
unestatic final
variable dont la valeur est connue au moment de la compilation. Si c’est le cas,false
le compilateur n’émettra aucun code pour l’if
instruction complète , ce qui en fera un finaliseur trivial (vide), qui à son tour signifie que votre classe sera traitée comme si elle n'avait pas de finaliseur. (En C #, cela se ferait avec un beau#if DEBUG
bloc, mais que pouvons-nous faire, java, où nous payons une simplicité apparente dans le code avec une surcharge supplémentaire dans le cerveau.)Pour en savoir plus sur la disposition obligatoire, avec des discussions supplémentaires sur la disposition des ressources dans dot net, ici: michael.gr: Élimination obligatoire ou abomination "Éliminer-éliminer"
la source
close()
, au cas où. Je crois que si l'on ne fait pas confiance à mes tests, je ferais mieux de ne pas mettre le système en production.if( Global.DEBUG && ...
construction fonctionne de manière à ce que la machine virtuelle Java ignore lafinalize()
méthode comme étant triviale,Global.DEBUG
doit être défini au moment de la compilation (par opposition à injecté, etc.), de sorte que ce qui suit sera du code mort. L'appel ensuper.finalize()
dehors du bloc if est également suffisant pour que la JVM le considère comme non-trivial (quelle que soit la valeur deGlobal.DEBUG
, du moins sur HotSpot 1.8), même si la super classe#finalize()
est également triviale!java.io
. Si ce type de sécurité des threads ne figurait pas sur la liste de souhaits, cela augmenterait les frais généraux induits parfinalize()
…Parce que les descripteurs de fichier et les connexions (c'est-à-dire les descripteurs de fichier sur les systèmes Linux et POSIX) sont une ressource assez rare (vous pouvez être limité à 256 sur certains systèmes ou à 16384 sur d'autres; voir setrlimit (2) ). Rien ne garantit que le GC sera appelé assez souvent (ou au bon moment) pour éviter d'épuiser des ressources aussi limitées. Et si le GC n’appelle pas assez (ou si la finalisation n’est pas exécutée au bon moment), vous atteindrez cette limite (éventuellement basse).
La finalisation est une opération "au mieux" dans la machine virtuelle. Cela pourrait ne pas être appelé, ou être appelé assez tard ... En particulier, si vous avez beaucoup de RAM ou si votre programme n'alloue pas beaucoup d'objets génération par copie d'une génération), le GC peut être appelé très rarement et les finalisations risquent de ne pas s'exécuter très souvent (voire même de ne pas s'exécuter du tout).
Donc
close
, descripteur de fichier explicitement, si possible. Si vous avez peur de les divulguer, utilisez la finalisation comme mesure supplémentaire et non comme principale.la source
Regardez la question de cette façon: vous ne devriez écrire que du code qui est (a) correct (sinon votre programme est tout simplement faux) et (b) nécessaire (sinon votre code est trop volumineux, ce qui signifie plus de RAM, plus de cycles sont nécessaires) des choses inutiles, plus d’efforts pour le comprendre, plus de temps à le maintenir, etc. etc.)
Considérons maintenant ce que vous voulez faire dans un finaliseur. Soit c'est nécessaire. Dans ce cas, vous ne pouvez pas le mettre dans un finaliseur, car vous ne savez pas s'il sera appelé. Cela ne suffit pas. Ou si ce n'est pas nécessaire - alors vous ne devriez pas l'écrire en premier lieu! Quoi qu'il en soit, le mettre dans le finaliseur est le mauvais choix.
(Notez que les exemples que vous nommez, comme les flux de fichiers fermer, regarder comme si elles ne sont pas vraiment nécessaires, mais ils sont. Il est juste que jusqu'à ce que vous atteignez la limite pour les descripteurs de fichiers ouverts sur votre système, vous ne serez pas un avis que votre le code est incorrect. Mais cette limite est une caractéristique du système d'exploitation et est donc encore plus imprévisible que la stratégie de la JVM pour les finaliseurs, il est donc vraiment important de ne pas gaspiller les descripteurs de fichiers.)
la source
finalize()
alors le fermer au cas où un cycle gc aurait vraiment besoin d'être libéré. RAM. Sinon, je garderais l'objet dans la RAM au lieu de devoir le régénérer et le fermer à chaque fois que je devrais l'utiliser. Bien sûr, les ressources qu’elles ouvrent ne seront libérées que lorsque l’objet est GCed, quel que soit le cas, mais je n’ai peut-être pas besoin de garantir que mes ressources soient libérées à un moment donné.L'une des principales raisons de ne pas compter sur les finaliseurs est que la plupart des ressources que l'on pourrait être tenté de nettoyer dans le finaliseur sont très limitées. Le ramasse-miettes ne s'exécute que de temps en temps, car le fait de parcourir des références pour déterminer si quelque chose peut être publié coûte cher. Cela signifie qu'il pourrait s'écouler un certain temps avant que vos objets soient réellement détruits. Si de nombreux objets ouvrent des connexions de base de données de courte durée, par exemple, laisser les finaliseurs nettoyer ces connexions pourrait épuiser votre pool de connexions en attendant que le garbage collector soit enfin exécuté et libère les connexions terminées. Ensuite, à cause de l'attente, vous vous retrouvez avec un important arriéré de demandes en file d'attente, qui épuisent rapidement le pool de connexions. Il'
De plus, l'utilisation de try-with-resources facilite la fermeture des objets 'pouvant être fermés'. Si vous n'êtes pas familier avec cette construction, je vous suggère de la vérifier: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
la source
En plus de la raison pour laquelle laisser au finaliseur le soin de libérer des ressources est généralement une mauvaise idée, les objets pouvant être finalisés sont soumis à une surcharge de performances.
D'après la théorie et la pratique de Java: le ramassage des ordures et les performances (Brian Goetz), les finaliseurs ne sont pas vos amis :
la source
finalize()
.Ma raison (la moins) préférée pour éviter
Object.finalize
n'est pas que les objets puissent être finalisés après que vous les attendiez, mais ils peuvent l'être avant que vous ne les attendiez. Le problème n'est pas qu'un objet toujours dans la portée puisse être finalisé avant que la portée ne soit quittée si Java décide qu'il n'est plus accessible.Voir cette question pour plus de détails. Ce qui est encore plus amusant, c’est que cette décision ne pourrait être prise qu’après l’optimisation des points névralgiques, ce qui rendrait le débogage difficile.
la source
HasFinalize.stream
devrait être lui-même un objet finalisable séparément. C'est-à-dire que la finalisation deHasFinalize
ne devrait pas être finalisée ou essayée de nettoyerstream
. Ou s'il le devrait, alors il devrait êtrestream
inaccessible.Dans Eclipse, je reçois un avertissement chaque fois que j'oublie de fermer quelque chose qui implémente
Closeable
/AutoCloseable
. Je ne sais pas si c'est un problème Eclipse ou s'il fait partie du compilateur officiel, mais vous pourriez envisager d'utiliser des outils d'analyse statique similaires pour vous aider. FindBugs, par exemple, peut probablement vous aider à vérifier si vous avez oublié de fermer une ressource.la source
AutoCloseable
. Il est très facile de gérer des ressources à l’essai avec des ressources. Cela annule plusieurs des arguments de la question.A votre première question:
Eh bien, la JVM déterminera le moment opportun pour récupérer le stockage alloué à un objet. Ce n'est pas nécessairement le moment où le nettoyage de la ressource que vous souhaitez effectuer
finalize()
doit avoir lieu. Ceci est illustré dans la question «SOI de finalize () appelée sur un objet très joignable dans Java 8» . Là, uneclose()
méthode a été appelée par unefinalize()
méthode, alors qu'une tentative de lecture du flux par le même objet est toujours en attente. Ainsi, outre la possibilité bien connue d'finalize()
être appelée trop tard, il est possible qu'elle soit appelée trop tôt.La prémisse de votre deuxième question:
est tout simplement faux. Aucune machine virtuelle Java n'est nécessaire pour prendre en charge la finalisation. Eh bien, ce n’est pas complètement faux, car vous pouvez toujours l’interpréter comme «l’application a été terminée avant la finalisation», en supposant que votre application se termine un jour.
Mais notez la petite différence entre «GC» de votre déclaration initiale et le terme «finalisation». La collecte des ordures est distincte de la finalisation. Une fois que la gestion de la mémoire a détecté qu’un objet est inaccessible, il peut simplement récupérer son espace, s’il n’a pas de
finalize()
méthode spéciale ou si la finalisation n’est tout simplement pas prise en charge, ou peut mettre en file d'attente l’objet pour finalisation. Ainsi, l'achèvement d'un cycle de récupération de place ne signifie pas que les finaliseurs sont exécutés. Cela peut arriver plus tard, lors du traitement de la file d'attente, ou jamais du tout.C'est également la raison pour laquelle, même sur les machines virtuelles avec prise en charge de la finalisation, il est dangereux de s'y fier pour le nettoyage des ressources. La récupération de place fait partie de la gestion de la mémoire et est donc déclenchée par des besoins en mémoire. Il est possible que la récupération de place ne s'exécute jamais car la mémoire est suffisante pendant toute la durée d'exécution (enfin, cela correspond toujours à la description «l'application a été arrêtée avant que le GC n'ait eu la chance de s'exécuter»). Il est également possible que le CPG s'exécute, mais qu'après, la mémoire est suffisamment récupérée pour que la file d'attente du finaliseur ne soit pas traitée.
En d'autres termes, les ressources natives gérées de cette manière sont toujours étrangères à la gestion de la mémoire. Bien qu'il soit garanti qu'un
OutOfMemoryError
est lancé uniquement après un nombre suffisant de tentatives pour libérer de la mémoire, cela ne s'applique pas aux ressources natives ni à la finalisation. Il est possible que l'ouverture d'un fichier échoue en raison de ressources insuffisantes alors que la file d'attente de finalisation est pleine d'objets susceptibles de libérer ces ressources, si le finaliseur s'exécutait un jour…la source