Question

J'ai lu beaucoup de questions sur les recrues en Java sur finalize () et je trouve assez ahurissant que personne ne nous ait 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'ai pas étudié le 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’elle n’a pas d’utilisation, ni que les personnes avec qui j'ai travaillé le font correctement.

Ma question est donc la suivante: quels sont les cas d'utilisation de la mise en œuvre de finalize () qui ne peuvent pas être gérés de manière plus fiable via un autre processus ou une autre syntaxe dans le langage?

Veuillez fournir des scénarios spécifiques ou votre expérience. Il ne suffit pas de répéter un livre de texte Java ou de finaliser l'utilisation prévue, ce n'est pas le but de cette question.

Était-ce utile?

La solution

Vous pouvez l’utiliser comme protection pour un objet contenant une ressource externe (socket, fichier, etc.). Implémentez une méthode close () et le document à appeler.

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 transféré dans stderr pour indiquer que vous nettoyez après un appelant qui a des problèmes.

Il offre une sécurité supplémentaire dans une situation exceptionnelle / buggy. Tous les appelants ne feront pas le bon essayer {} finally {} à chaque fois. Malheureusement, 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. À utiliser uniquement si vous avez besoin de cette "ceinture et bretelles". sécurité dans une application de longue durée.

Je vois cela depuis Java 9, Object.finalize () est obsolète! Ils nous indiquent java.lang .ref.Cleaner et java.lang.ref.PhantomReference comme alternatives.

Autres conseils

finalize () indique à la machine virtuelle Java qu'il peut être intéressant 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:

  • vous voulez parier que d'autres objets finalisés resteront dans un état que le reste de votre programme jugera valide.
  • vous voulez ajouter beaucoup de code de vérification à toutes les méthodes de toutes vos classes qui ont un finaliseur, pour vous assurer qu'elles se comportent correctement après la finalisation.
  • vous souhaitez ressusciter accidentellement des objets finalisés et passez beaucoup de temps à essayer de comprendre pourquoi ils ne fonctionnent pas et / ou pourquoi ils ne sont pas finalisés quand ils sont finalement libérés.

Si vous pensez avoir besoin de finalize (), il vous faut parfois une référence fantôme (qui, dans l'exemple donné, pourrait contenir une référence fixe à une connexion utilisée par son referand et la fermer. après que la référence fantôme a été mise en file d'attente). 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 - quel est l’intérêt de disposer d’un ramasse-miettes si vous concevez des interfaces qui requièrent qu’une action spécifique soit effectuée avant la collecte? Cela nous ramène à l’époque de malloc / free.)

D'autres fois, vous avez besoin de la ressource que vous pensez être en mesure de devenir 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: n'utilisez jamais de finaliseurs.

Le seul fait qu'un objet dispose d'un finaliseur (quel que soit le code qu'il exécute) est suffisant pour causer une surcharge considérable au nettoyage de la mémoire.

Extrait d'un article de Brian Goetz:

  

Objets avec finaliseurs (ceux qui   avoir une méthode finalize () non triviale)   avoir des frais généraux importants par rapport à   objets sans finaliseurs, et devrait   être utilisé avec parcimonie. Finalisable   les objets sont à la fois plus lents à allouer   et plus lent à collecter. À l'attribution   temps, la machine virtuelle Java doit enregistrer   objets définissables à la poubelle   collecteur et (au moins dans le   HotSpot JVM)   les objets finalisables doivent suivre un   chemin d'allocation plus lent que la plupart des autres   objets. De même, définissable   Les objets sont également plus lents à collecter. Il   prend au moins deux poubelles   cycles (dans le meilleur des cas) avant une   l'objet définissable 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é   attribution et la collecte d'objets et   plus de pression sur les ordures   collecteur, car la mémoire utilisée par   objets finalisables inaccessibles est   retenu plus longtemps. Combinez cela avec le   fait que les finaliseurs ne sont pas   garanti pour fonctionner dans tout prévisible   ou même pas du tout, et vous pouvez   voir qu'il y a relativement peu de   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é d'implémenter 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

Je suppose donc que le Soleil a trouvé des cas dans lesquels (ils pensent) qu'il devrait être utilisé.

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 le " Référence " Des classes. Référence faible, Référence fantôme & amp; Référence souple.

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 lui faire appeler une méthode quand elle sera supprimée, et cette méthode peut être garantie d'être appelée.

Comme pour finaliser: J'ai utilisé finalize une fois pour comprendre quels objets étaient libérés. Vous pouvez jouer à des jeux ordonnés avec des statistiques, des comptages de références, etc. - mais c'était uniquement pour l'analyse, mais méfiez-vous du code comme celui-ci (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. " Nettoyage " 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 quelque raison que ce soit doit être libérée manuellement avant que l'objet ne soit converti). . Généralement, c'est parce que quelqu'un a oublié de supprimer un auditeur ou quelque chose du genre et qu'il ne comprend pas 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 rapidement".

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:

This is finalize

MyObject still alive!

=======================================

Vous pouvez donc rendre une instance inaccessible accessible dans la méthode finalize.

finalize () 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 la version 1.0 alpha 3 (1995) et je n’ai pas encore annulé la finalisation pour 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 n’aura pas été 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 sophistiquée.

Il est préférable d'éviter les diagnostics test-env.

Pensée en Java d’Ekel a une bonne section sur ce sujet.

Hmmm, je l’utilisais jadis pour nettoyer des objets qui n’étaient pas renvoyés dans un pool existant.

Ils ont beaucoup circulé, 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 fait avec.

Faites attention à ce que vous faites dans un finalize () . Surtout si vous l'utilisez entre autres pour appeler close () afin de 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 corruption très mauvaise 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’est juste le fait que nous appelions les méthodes JNI à partir de finalize ().

Il s’agissait d’un JDK 1.5 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.

Lors de l'écriture d'un code qui sera utilisé par d'autres développeurs et qui nécessite une sorte de "nettoyage". méthode à appeler 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 utilisent cette fonctionnalité dans leurs implémentations Statement et Connection pour offrir 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 mise en œuvre 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:

    private void addGlobalClickListener() {
        weakAwtEventListener = new WeakAWTEventListener(this);

        Toolkit.getDefaultToolkit().addAWTEventListener(weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();

        if(weakAwtEventListener != null) {
            Toolkit.getDefaultToolkit().removeAWTEventListener(weakAwtEventListener);
        }
    }

iirc - vous pouvez utiliser la méthode de finalisation pour implémenter un mécanisme de regroupement des ressources coûteuses - afin d'éviter de recevoir les GC également.

En note de bas de page:

  

Un objet qui annule 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 la méthode finalize () terminée, l'objet sera enfin prêt pour le ramassage des ordures lors du prochain cycle.

Source: finalize () obsolète sur java-9

Les ressources (Fichier, Socket, Stream, etc.) doivent être fermées une fois que nous en avons fini. 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 également ê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

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 également implémenter Closeable . dans nos propres classes et l'utiliser de manière similaire. OMI cela semble plus soigné et simple à comprendre.

Personnellement, je n'ai presque jamais utilisé 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 personnalisée finalize () . cela fait ce qui suit:

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, comme #finalize () , #hashCode () et #clone () )

Ainsi, en utilisant une méthode soeur #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 pourrait garder la JVM en vie par inadvertance. J'ai aussi envisagé d'arrêter tous les fils de discussion, mais cela a ouvert une nouvelle boîte de Pandore.

La liste des réponses acceptées indique qu'il est possible de fermer une ressource lors de la finalisation.

Cependant, cette réponse montre qu'au moins dans java8 avec le compilateur JIT, vous rencontrez des problèmes inattendus dans lesquels le finaliseur est appelé avant même d'avoir fini de lire un flux maintenu par votre objet.

Ainsi, même dans cette situation, le fait d'appeler à la finalisation ne serait pas recommandé.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top