J'ai lu beaucoup de questions sur les recrues de Java sur finalize () et je trouve assez déroutant de constater que personne n'a vraiment expliqué que finalize () est un moyen peu fiable de nettoyer les ressources. J'ai vu quelqu'un commenter qu'ils l'utilisaient pour nettoyer Connections, ce qui est vraiment effrayant car le seul moyen de s'approcher autant de la garantie de la fermeture d'une connexion est d'implémenter try (catch).
Je n'avais pas étudié en CS, mais je programme professionnellement en Java depuis près de dix ans et je n'ai jamais vu personne implémenter finalize () dans un système de production. Cela ne signifie toujours pas qu'il n'a pas d'utilisation, ou que les personnes avec qui j'ai travaillé l'ont bien fait.
Ma question est donc la suivante: quels sont les cas d’utilisation pour l’implémentation de finalize () qui ne peuvent pas être gérés de manière plus fiable via un autre processus ou une autre syntaxe au sein du langage?
Veuillez fournir des scénarios spécifiques ou votre expérience, répéter simplement un livre de texte Java ou l’utilisation prévue de Finalize n’est pas suffisant et n’est pas l’intention de cette question.
Vous pouvez l'utiliser comme protection pour un objet contenant une ressource externe (socket, fichier, etc.). Implémentez une méthode close()
et documentez-la.
Implémentez finalize()
pour effectuer le traitement close()
si vous détectez que cela n'a pas été fait. Peut-être que quelque chose sera envoyé à stderr pour indiquer que vous nettoyez après un appelant buggy.
Il offre une sécurité supplémentaire dans une situation exceptionnelle/buggy. Tous les appelants ne vont pas toujours faire les bons trucs try {} finally {}
. Malheureux, mais vrai dans la plupart des environnements.
Je conviens que c'est rarement nécessaire. Et, comme le soulignent les commentateurs, les frais généraux du GC sont inclus. Utilisez uniquement si vous avez besoin de cette sécurité "ceinture et bretelles" dans une application de longue durée.
Je vois que depuis Java 9, Object.finalize()
est obsolète! Ils nous indiquent Java.lang.ref.Cleaner
et Java.lang.ref.PhantomReference
comme alternatives.
finalize () est un indice pour la JVM qu'il peut être agréable d'exécuter votre code à une heure non spécifiée. C'est bien quand vous voulez que le code échoue mystérieusement.
Faire quelque chose d'important dans les finaliseurs (essentiellement tout sauf la journalisation) est également utile dans trois situations
Si vous pensez avoir besoin de finalize (), vous voulez parfois une référence fantôme (qui dans l'exemple donné peut contenir une référence fixe à une connexion utilisée par son référant et la fermer après la mise en file d'attente de la référence fantôme). Cela a également la propriété de ne jamais mystérieusement être exécutée, mais au moins, il ne peut pas appeler de méthodes ou ressusciter des objets finalisés. C'est donc juste pour les situations où vous n'avez pas absolument besoin de fermer cette connexion proprement, mais vous aimeriez bien, et les clients de votre classe ne peuvent pas ou ne veulent pas s'approcher d'eux-mêmes (ce qui est en fait assez juste - à quoi ça sert d'avoir un ramasse-miettes du tout si vous concevez des interfaces qui nécessitent une action spécifique soit entreprise avant la collecte? Cela nous ramène à l'époque de malloc/free.)
D'autres fois, vous avez besoin de la ressource que vous pensez gérer pour être plus robuste. Par exemple, pourquoi avez-vous besoin de fermer cette connexion? Il doit en fin de compte être basé sur une sorte d’E/S fournie par le système (socket, fichier, peu importe), alors pourquoi ne pouvez-vous pas compter sur le système pour le fermer à votre place lorsque le niveau de ressources le plus bas est atteint? Si le serveur situé à l'autre extrémité exige absolument que vous fermiez la connexion proprement plutôt que de simplement laisser tomber le socket, que se passera-t-il si quelqu'un trébuche sur le câble d'alimentation de la machine sur laquelle votre code est exécuté ou si le réseau intermédiaire s'éteint?
Avertissement: j'ai déjà travaillé sur une implémentation de machine virtuelle Java. Je déteste les finaliseurs.
Une règle simple: ne jamais utiliser de finaliseurs. Le seul fait qu’un objet ait un finaliseur (quel que soit le code qu’il exécute) est suffisant pour causer une surcharge considérable à la récupération de place.
Extrait d'un article de Brian Goetz:
Objets avec finaliseurs (ceux qui Ont une méthode finalize () non triviale) ont des frais généraux importants par rapport à objets sans finaliseurs, et devrait être utilisé avec parcimonie. Finalisable les objets sont plus lents à allouer et plus lent à collecter. À l'attribution JVM doit enregistrer n'importe quel fichier objets définissables à la poubelle collecteur, et (au moins dans l’implémentation de la machine virtuelle Java HotSpot) les objets finalisables doivent suivre un chemin d'allocation plus lent que la plupart des autres objets. De même, finalisable Les objets sont plus lents à collecter aussi. Il prend au moins deux poubelles cycles (dans le meilleur des cas) avant un objet finalisable peut être récupéré, et le ramasse-miettes doit faire travail supplémentaire pour invoquer le finaliseur . Le résultat est plus de temps passé attribuer et collecter des objets et plus de pression sur les ordures collecteur, car la mémoire utilisée par les objets finalisables inaccessibles sont retenu plus longtemps. Combinez cela avec le fait que les finaliseurs ne sont pas garanti de fonctionner dans tout prévisible ou même pas du tout, et vous pouvez voyez qu'il y en a relativement peu situations pour lesquelles la finalisation est le bon outil à utiliser.
La seule fois que j'ai utilisé finalize dans le code de production a été de mettre en œuvre une vérification du nettoyage des ressources d'un objet donné. Sinon, enregistrez un message très vocal. Il n'a pas réellement essayé de le faire lui-même, il a simplement crié si ce n'était pas fait correctement. S'est avéré très utile.
Je fais Java professionnellement depuis 1998 et je n'ai jamais implémenté finalize (). Pas une fois.
Je ne sais pas ce que vous pouvez en dire, mais ...
itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.Java" | xargs grep "void finalize()" | wc -l
41
Donc, je suppose que le soleil a trouvé certains des cas où (ils pensent) qu'il devrait être utilisé.
J'ai utilisé finalize une fois pour comprendre quels objets étaient libérés. Vous pouvez jouer à des jeux ordonnés avec la statique, le comptage de références, etc., mais ce n'était que pour l'analyse.
La réponse acceptée est bonne, je voulais juste ajouter qu’il existe désormais un moyen de disposer de la fonctionnalité de finalisation sans l’utiliser du tout.
Regardez les classes "Référence". Référence faible, etc.
Vous pouvez les utiliser pour conserver une référence à tous vos objets, mais cette référence SEULE n'arrêtera pas GC. La chose intéressante à propos de cela est que vous pouvez le faire appeler une méthode quand elle sera supprimée, et cette méthode peut être garantie d'être appelée.
Une autre chose à surveiller. Chaque fois que vous voyez quelque chose comme ceci n'importe où dans le code (pas seulement en phase de finalisation, mais c'est là que vous êtes le plus susceptible de le voir):
public void finalize() {
ref1 = null;
ref2 = null;
othercrap = null;
}
C'est un signe que quelqu'un ne savait pas ce qu'il faisait. "Nettoyer" comme cela n'est pratiquement jamais nécessaire. Lorsque le cours est GC'd, cela se fait automatiquement.
Si vous trouvez un code comme celui-ci dans une finalisation, il est garanti que la personne qui l'a écrit était confuse.
Si c'est ailleurs, il se peut que le code soit un correctif valide pour un mauvais modèle (une classe reste longtemps et pour une raison quelconque, les éléments auxquels elle faisait référence ont dû être libérés manuellement avant que l'objet ne soit converti au format GC). Généralement, c'est parce que quelqu'un a oublié de retirer un auditeur ou quelque chose du genre et ne peut pas comprendre pourquoi son objet n'est pas numérisé. Il supprime simplement les éléments auxquels il fait référence, hausse les épaules et s'en va.
Il ne devrait jamais être utilisé pour nettoyer les choses "plus vite".
class MyObject {
Test main;
public MyObject(Test t) {
main = t;
}
protected void finalize() {
main.ref = this; // let instance become reachable again
System.out.println("This is finalize"); //test finalize run only once
}
}
class Test {
MyObject ref;
public static void main(String[] args) {
Test test = new Test();
test.ref = new MyObject(test);
test.ref = null; //MyObject become unreachable,finalize will be invoked
System.gc();
if (test.ref != null) System.out.println("MyObject still alive!");
}
}
====================================
résultat:
C'est finaliser
MyObject toujours en vie!
=====================================
Vous pouvez donc rendre une instance inaccessible accessible dans la méthode finalize.
finaliser peut être utile pour détecter les fuites de ressources. Si la ressource doit être fermée mais n'est pas écrite, écrivez le fait qu'elle n'a pas été fermée à un fichier journal et fermez-le. De cette façon, vous supprimez la fuite de ressources et vous vous donnez un moyen de savoir que cela s'est passé pour pouvoir y remédier.
Je programme en Java depuis 1.0 alpha 3 (1995) et je n’ai pas encore finalisé pour finaliser quoi que ce soit ...
Vous ne devriez pas dépendre de finalize () pour nettoyer vos ressources pour vous. finalize () ne fonctionnera pas tant que la classe ne sera pas nettoyée. Il est bien mieux de libérer explicitement les ressources lorsque vous avez fini de les utiliser.
Pour mettre en évidence un point dans les réponses ci-dessus: les finaliseurs seront exécutés sur le seul thread GC. J'ai entendu parler d'une grande démo de Sun où les développeurs ont ajouté une petite couche de sommeil à certains finaliseurs et ont intentionnellement mis à genoux une démo 3D élégante.
Il est préférable d'éviter, avec une possible exception des diagnostics test-env.
Eckel's Thinking in Java a une bonne section à ce sujet.
Faites attention à ce que vous faites dans une finalize (). Surtout si vous l'utilisez pour des choses telles que l'appel de close () pour vous assurer que les ressources sont nettoyées. Nous avons rencontré plusieurs situations dans lesquelles des bibliothèques JNI étaient liées au code Java en cours d’exécution. Dans tous les cas où nous utilisions finalize () pour appeler des méthodes JNI, nous obtenions une très grave corruption du tas Java. La corruption n'a pas été causée par le code JNI sous-jacent, toutes les traces de mémoire étaient correctes dans les bibliothèques natives. C'était juste le fait que nous appelions les méthodes JNI à partir de finalize ().
C'était avec un JDK 1.5 qui est encore largement utilisé.
Nous ne découvririons pas que quelque chose n'allait pas avant bien plus tard, mais à la fin, le coupable était toujours la méthode finalize () utilisant les appels JNI.
Hmmm, je l'ai utilisé une fois pour nettoyer des objets qui n'étaient pas retournés dans un pool existant. Ils ont été beaucoup distribués, il était donc impossible de dire quand ils pourraient être renvoyés en toute sécurité à la piscine. Le problème était que cela entraînait une pénalité énorme lors de la collecte des ordures bien supérieure aux économies réalisées grâce à la mise en commun des objets. Il était en production depuis environ un mois avant que je déchire toute la piscine, que tout soit dynamique et que tout soit terminé.
Lors de l'écriture d'un code qui sera utilisé par d'autres développeurs, il est nécessaire d'appeler une méthode de "nettoyage" pour libérer des ressources. Parfois, ces autres développeurs oublient d'appeler votre méthode de nettoyage (ou de fermeture, de destruction ou autre). Pour éviter d'éventuelles fuites de ressources, vous pouvez archiver la méthode finalize pour vous assurer que la méthode a été appelée. Sinon, vous pouvez l'appeler vous-même.
De nombreux pilotes de base de données le font dans leurs implémentations Statement et Connection pour assurer un peu de sécurité contre les développeurs qui oublient d'appeler de près.
Edit: OK, ça ne marche vraiment pas. Je l'ai implémentée et j'ai pensé que si cela échouait parfois, tout allait bien pour moi, mais la méthode de finalisation n'a même pas été appelée une seule fois.
Je ne suis pas un programmeur professionnel, mais dans mon programme, je pense que c'est un bon exemple d'utilisation de finalize (), c'est-à-dire un cache qui écrit son contenu sur le disque avant sa destruction. Parce qu'il n'est pas nécessaire qu'il soit exécuté à chaque destruction, cela ne fait qu'accélérer mon programme, j'espère que je ne l'ai pas mal fait.
@Override
public void finalize()
{
try {saveCache();} catch (Exception e) {e.printStackTrace();}
}
public void saveCache() throws FileNotFoundException, IOException
{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
out.writeObject(cache);
}
Il peut être utile de supprimer des éléments ajoutés à un emplacement global/statique (par nécessité) et devant être supprimés lorsque l'objet est supprimé. Par exemple:
void privé addGlobalClickListener () { faibleAwtEventListener = nouveau WeakAWTEventListener (this); Toolkit.getDefaultToolkit (). AddAWTEventListener (faibleAwtEventListener, AWTEvent.MOUSE_EVENT_MASK); } @Passer outre void protected finalize () jette Throwable { super.finalize (); if (faibleAwtEventListener! = null) { Toolkit.getDefaultToolkit (). RemoveAWTEventListener (faibleAwtEventListener); } }
En note de côté:
Un objet qui remplace finalize () est traité spécialement par le ramasse-miettes. Généralement, un objet est immédiatement détruit pendant le cycle de collecte une fois que l'objet n'est plus dans la portée. Cependant, les objets finalisables sont plutôt déplacés vers une file d'attente, où des threads de finalisation distincts vont vider la file d'attente et exécuter la méthode finalize () sur chaque objet. Une fois que la méthode finalize () se termine, l'objet sera enfin prêt pour la récupération de place dans le cycle suivant.
iirc - vous pouvez utiliser la méthode de finalisation pour mettre en œuvre un mécanisme de mise en commun des ressources coûteuses - afin qu'ils ne reçoivent pas les GC également.
Personnellement, je n'utilise presque jamais finalize()
, sauf dans un cas rare: j'ai créé une collection personnalisée de type générique et j'ai écrit une méthode finalize()
personnalisée qui effectue les opérations suivantes:
public void finalize() throws Throwable {
super.finalize();
if (destructiveFinalize) {
T item;
for (int i = 0, l = length(); i < l; i++) {
item = get(i);
if (item == null) {
continue;
}
if (item instanceof Window) {
((Window) get(i)).dispose();
}
if (item instanceof CompleteObject) {
((CompleteObject) get(i)).finalize();
}
set(i, null);
}
}
}
(CompleteObject
est une interface que j'ai créée et qui vous permet de spécifier que vous avez implémenté des méthodes Object
rarement implémentées, telles que #finalize()
, #hashCode()
et #clone()
)
Ainsi, en utilisant une méthode sister #setDestructivelyFinalizes(boolean)
, le programme utilisant ma collection peut (aider) garantir que la destruction d’une référence à cette collection détruit également les références à son contenu et supprime les fenêtres qui pourraient maintenir la JVM en vie par inadvertance. J'ai aussi envisagé d'arrêter les discussions, mais cela a ouvert une nouvelle boîte de Pandore.
Les ressources (Fichier, Socket, Stream, etc.) doivent être fermées une fois l'utilisation terminée. Ils ont généralement la méthode close()
que nous appelons généralement dans la section finally
des instructions try-catch
. Parfois, finalize()
peut aussi être utilisé par quelques développeurs, mais IMO n’est pas un moyen approprié, car rien ne garantit que finalize sera toujours appelé.
En Java 7, nous avons try-with-resources statement qui peut être utilisé comme:
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
// Processing and other logic here.
} catch (Exception e) {
// log exception
} finally {
// Just in case we need to do some stuff here.
}
Dans l'exemple ci-dessus, try-with-resource fermera automatiquement la ressource BufferedReader
en appelant la méthode close()
. Si nous le souhaitons, nous pouvons aussi implémenter Closeable dans nos propres classes et l’utiliser de la même manière. OMI cela semble plus soigné et simple à comprendre.
Les réponses acceptées indiquent que la fermeture d’une ressource pendant la finalisation peut être effectuée.
Cependant cette réponse montre qu'au moins en Java8 avec le compilateur JIT, vous rencontrez des problèmes inattendus dans lesquels le finaliseur est parfois appelé avant même d'avoir fini de lire un flux géré par votre objet.
Ainsi, même dans cette situation, l’appel de finalisation ne serait pas recommandé.