web-dev-qa-db-fra.com

Existe-t-il un destructeur pour Java?

Existe-t-il un destructeur pour Java? Je ne semble pas pouvoir trouver de documentation à ce sujet. S'il n'y en a pas, comment puis-je obtenir le même effet?

Pour préciser ma question, je suis en train d’écrire une application qui traite des données et la spécification indique qu’il devrait y avoir un bouton de «réinitialisation» qui ramène l’application à son état initial. Cependant, toutes les données doivent être actives, à moins que l'application ne soit fermée ou que le bouton de réinitialisation ne soit enfoncé.

Étant habituellement un programmeur C/C++, je pensais que ce serait facile à implémenter. (Et par conséquent, j'avais prévu de le mettre en œuvre en dernier.) J'ai structuré mon programme de telle sorte que tous les objets pouvant être réinitialisés soient dans la même classe, de sorte que je puisse simplement détruire tous les objets "en direct" lorsqu'un bouton de réinitialisation est enfoncé.

Je pensais que si tout ce que je faisais était simplement de déréférencer les données et d'attendre que le ramasse-miettes les récupère, n'y aurait-il pas une fuite de mémoire si mon utilisateur saisissait à plusieurs reprises des données et appuyait sur le bouton de réinitialisation? Je pensais aussi que Java étant un langage assez évolué, il devrait exister un moyen d’empêcher que cela ne se produise ou d’y remédier avec élégance.

530
wai

Comme Java est un langage mal ordonné, vous ne pouvez pas prédire quand (ou même si) un objet sera détruit. Il n'y a donc pas d'équivalent direct d'un destructeur.

Il existe une méthode héritée appelée finalize, mais elle est entièrement appelée à la discrétion du garbage collector. Donc, pour les classes qui ont besoin de ranger explicitement, la convention est de définir une méthode close et d’utiliser finalize uniquement pour le contrôle de cohérence (c’est-à-dire si close n’a pas été appelé, faites-le maintenant et enregistrez une erreur).

Il y avait une question qui a engendré une discussion en profondeur de finaliser récemment, donc cela devrait fournir plus de profondeur si nécessaire ...

479
Garth Gilmour

Si vous utilisez Java 7, consultez l'instruction try-with-resources . Par exemple:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  System.out.println(br.readLine());
} catch (Exception e) {
  ...
} finally {
  ...
}

Ici, la ressource qui n'est plus nécessaire est libérée dans la méthode BufferedReader.close(). Vous pouvez créer votre propre classe qui implémente AutoCloseable et l'utiliser de la même manière. 

Cette instruction est plus limitée que finalize en termes de structuration de code, mais elle simplifie en même temps la compréhension et la maintenance du code. De plus, rien ne garantit qu'une méthode finalize soit appelée pendant la durée de vie de l'application.

108
Vitalii Fedorenko

Non, pas de destructeurs ici. La raison en est que tous les objets Java sont alloués et ramassés. Sans désallocation explicite (c'est-à-dire l'opérateur de suppression de C++), il n'existe aucun moyen sensé d'implémenter de vrais destructeurs.

Java prend en charge les finaliseurs, mais ils sont conçus pour être utilisés uniquement comme protection pour les objets contenant un descripteur de ressources natives telles que les sockets, les descripteurs de fichier, les descripteurs de fenêtre, etc. Lorsque le garbage collector collecte un objet sans finaliseur, il marque simplement la mémoire région libre et c'est tout. Lorsque l'objet dispose d'un finaliseur, il est d'abord copié dans un emplacement temporaire (rappelez-vous, nous sommes en train de ramasser des ordures ici), puis il est mis en file d'attente dans une file d'attente en attente de finalisation, puis un thread de finalisation interroge la file avec une priorité très faible et exécute le finaliseur.

Lorsque l'application se ferme, la machine virtuelle Java s'arrête sans attendre que les objets en attente soient finalisés. Il n'y a donc pratiquement aucune garantie que vos finaliseurs seront exécutés.

107
ddimitrov

L'utilisation des méthodes finalize () doit être évitée. Ils ne constituent pas un mécanisme fiable pour le nettoyage des ressources et il est possible de causer des problèmes dans le récupérateur de mémoire en les abusant.

Si vous avez besoin d'un appel de désallocation dans votre objet, par exemple pour libérer des ressources, utilisez un appel de méthode explicite. Cette convention est visible dans les API existantes (par exemple, Closeable , Graphics.dispose () , Widget.dispose () ) et est généralement appelée via try/finally.

Resource r = new Resource();
try {
    //work
} finally {
    r.dispose();
}

Les tentatives d'utilisation d'un objet supprimé doivent générer une exception d'exécution (voir IllegalStateException ).


MODIFIER:

Je pensais, si tout ce que je faisais était juste déréférencer les données et attendre le ramasse-miettes pour les collecter, n'y aurait-il pas une fuite de mémoire si mon l’utilisateur a saisi plusieurs fois les données et appuyé sur le bouton de réinitialisation?

En général, tout ce que vous avez à faire est de déréférencer les objets - du moins, c’est ainsi que cela est censé fonctionner. Si vous êtes inquiet au sujet de la récupération de place, consultez Java SE 6 HotSpot [tm] Réglage de la récupération de place pour machine virtuelle (ou un document équivalent pour votre version de JVM).

25
McDowell

Avec Java 1.7, vous avez maintenant l’option supplémentaire d’utiliser le bloc try-with-resources. Par exemple, 

public class Closeable implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("closing..."); 
    }
    public static void main(String[] args) {
        try (Closeable c = new Closeable()) {
            System.out.println("trying..."); 
            throw new Exception("throwing..."); 
        }
        catch (Exception e) {
            System.out.println("catching..."); 
        }
        finally {
            System.out.println("finalizing..."); 
        } 
    }
}

Si vous exécutez cette classe, c.close() sera exécuté lorsque le bloc try sera quitté et avant que les blocs catch et finally ne soient exécutés. Contrairement à la méthode finalize(), l'exécution de close() est garantie. Cependant, il n'est pas nécessaire de l'exécuter explicitement dans la clause finally.

22
waku

Je suis entièrement d'accord avec les autres réponses, en disant de ne pas compter sur l'exécution de finaliser.

En plus des blocs try-catch-finally, vous pouvez utiliser Runtime # addShutdownHook (introduit en Java 1.6) pour effectuer des nettoyages finaux dans votre programme. 

Ce n'est pas la même chose que les destructeurs}, mais on peut implémenter un crochet d'arrêt ayant des objets d'écoute enregistrés sur lesquels des méthodes de nettoyage (fermer des connexions persistantes à la base de données, supprimer les verrous de fichiers, etc.) peuvent être invoquées. que se ferait normalement dans les destructeurs} _. Encore une fois - ce n'est pas un remplacement pour les constructeurs mais dans certains cas, vous pouvez aborder la fonctionnalité désirée avec ceci.

L'avantage de ceci est d'avoir un comportement de déconstruction faiblement couplé du reste de votre programme.

13
Markus Lux

Non, Java.lang.Object#finalize est le plus proche que vous puissiez obtenir.

Cependant, quand (et si) il est appelé, ce n’est pas garanti.
Voir: Java.lang.Runtime#runFinalizersOnExit(boolean)

9
flicken

Je suis désolé si cela ne correspond pas au sujet principal, mais la documentation de Java.util.Timer (SE6) indique:

"Une fois que la dernière référence active à un objet Timer a disparu et que l'exécution de toutes les tâches en attente est terminée, le fil d'exécution de la tâche du minuteur se termine normalement (et devient sujet à un garbage collection). Toutefois, cette opération peut prendre un temps Le thread d'exécution de tâche ne s'exécute pas en tant que thread démon, il est donc capable d'empêcher une application de se terminer. Si un appelant souhaite terminer rapidement le thread d'exécution de tâche d'un minuteur, il doit invoquer la méthode d'annulation du minuteur ... " 

Je voudrais appeler annuler la classe propriétaire du minuteur perdant sa dernière référence (ou immeditalesky auparavant). Ici, un destructeur fiable pourrait le faire pour moi. Les commentaires ci-dessus indiquent que le choix est finalement mauvais, mais existe-t-il une solution élégante? Cette affaire de "... capable d'empêcher une application de se terminer ..." n'est pas attrayante.

6

Tout d’abord, notez que, comme Java est collecté, il est rare de devoir faire quoi que ce soit à propos de la destruction d’objets. Tout d'abord parce que vous n'avez généralement pas de ressources gérées à libérer, et deuxièmement parce que vous ne pouvez pas prédire quand et si cela se produira, il est donc inapproprié pour les choses qui doivent se produire "dès que personne n'utilise plus mon objet ".

Vous pouvez être averti après la destruction d'un objet à l'aide de Java.lang.ref.PhantomReference (en fait, il peut être légèrement inexact de dire qu'il a été détruit, mais si une référence fantôme est mise en file d'attente, elle n'est plus récupérable, ce qui revient généralement à la même chose). Un usage courant est:

  • Séparez les ressources de votre classe qui doivent être détruites dans un autre objet d'assistance (notez que si vous ne faites que fermer une connexion, ce qui est un cas courant, vous n'avez pas besoin d'écrire une nouvelle classe: la connexion à fermer serait "l'objet d'assistance" dans ce cas).
  • Lorsque vous créez votre objet principal, créez également un objet PhantomReference. Faites-en référence au nouvel objet d'assistance ou configurez une carte à partir d'objets PhantomReference vers leurs objets d'assistance correspondants.
  • Une fois l'objet principal collecté, PhantomReference est mis en file d'attente (ou plutôt, il peut l'être, comme les finaliseurs, rien ne garantit qu'il le sera jamais. Par exemple, si VM se ferme sans attendre). Assurez-vous que vous traitez sa file d'attente (dans un thread spécial ou de temps en temps). En raison de la référence difficile à l'objet d'assistance, l'objet d'assistance n'a pas encore été collecté. Faites donc le nettoyage de votre choix sur l'objet d'assistance, puis supprimez le PhantomReference et celui-ci sera éventuellement collecté.

Il existe également finalize (), qui ressemble à un destructeur mais ne se comporte pas comme tel. Ce n'est généralement pas une bonne option.

6
Steve Jessop

Je suis d'accord avec la plupart des réponses.

Vous ne devriez pas dépendre entièrement de finalize ou ShutdownHook

finaliser

  1. JVM ne garantit pas que cette méthode finalize() sera appelée.

  2. finalize() est appelé une seule fois par le thread GC si l'objet se remet de la méthode de finalisation, alors que finalize ne sera pas appelé à nouveau.

  3. Dans votre application, vous pouvez avoir des objets vivants sur lesquels le garbage collection n'est jamais appelé.

  4. Toute exception levée par la méthode de finalisation est ignorée par le thread du GC

  5. Les méthodes System.runFinalization(true) et Runtime.getRuntime().runFinalization(true) augmentent la probabilité d'invoquer la méthode finalize(), mais ces deux méthodes sont désormais obsolètes. Ces méthodes sont très dangereuses en raison du manque de sécurité des threads et de la création éventuelle d'une impasse. 

shutdownHooks

public void addShutdownHook(Thread hook)

Enregistre un nouveau crochet d'arrêt de la machine virtuelle.

La machine virtuelle Java s'arrête en réponse à deux types d'événements:

  1. Le programme se ferme normalement, à la sortie du dernier thread non démon ou à l'invocation de la méthode exit (de manière équivalente, System.exit), ou
  2. La machine virtuelle est terminée en réponse à une interruption de l'utilisateur, telle que la saisie de ^ C, ou à un événement système, tel que la fermeture de session de l'utilisateur ou l'arrêt du système.
  3. Un hook d'arrêt est simplement un thread initialisé mais non démarré. Lorsque la machine virtuelle commence sa séquence d'arrêt, elle lancera tous les points d'ancrage d'arrêt enregistrés dans un ordre non spécifié et les laissera s'exécuter simultanément. Une fois tous les points d'accroche terminés, tous les finaliseurs non invoqués seront exécutés si la finalisation à la sortie a été activée. 
  4. Enfin, la machine virtuelle va s’arrêter. Notez que les threads démon continueront à s'exécuter pendant la séquence d'arrêt, de même que les threads non démon si l'arrêt a été lancé en appelant la méthode exit.
  5. Les crochets d'arrêt doivent également terminer leur travail rapidement. Lorsqu'un programme appelle exit, on s'attend à ce que la machine virtuelle s'arrête et se ferme rapidement.

    Mais même la documentation Oracle citait cela 

Dans de rares cas, la machine virtuelle peut abandonner, c'est-à-dire arrêter de fonctionner sans s'éteindre proprement

Cela se produit lorsque la machine virtuelle est arrêtée en externe, par exemple avec le signal SIGKILL sous Unix ou l'appel TerminateProcess sous Microsoft Windows. La machine virtuelle peut également abandonner si une méthode native échoue, par exemple en corrompant des structures de données internes ou en tentant d'accéder à une mémoire inexistante. Si la machine virtuelle est abandonnée, aucune garantie ne peut être donnée quant à l'exécution ou non de points d'ancrage.

Conclusion: utilisez les blocs try{} catch{} finally{} de manière appropriée et libérez les ressources critiques dans le bloc finally(}. Pendant la libération des ressources dans le bloc finally{}, interceptez les variables Exception et Throwable.

4
Ravindra babu

Si ce n'est que de la mémoire qui vous inquiète, ne le faites pas. Faites simplement confiance au GC, il fait un travail décent. En fait, j'ai constaté que le système était tellement efficace qu'il pouvait être plus performant de créer des tas d'objets minuscules que d'utiliser de grands tableaux dans certains cas.

4
John Nilsson

La fonction finalize() est le destructeur.

Cependant, il ne devrait pas être utilisé normalement car il est appelé après le GC et vous ne pouvez pas dire quand cela se produira (si jamais).

De plus, il faut plusieurs CPG pour désallouer des objets dotés de finalize().

Vous devriez essayer de nettoyer les endroits logiques de votre code en utilisant les instructions try{...} finally{...}!

4
Shimi Bandiel

Vous pouvez peut-être utiliser un bloc try ... finally pour finaliser l'objet dans le flux de contrôle auquel vous utilisez l'objet. Bien sûr, cela ne se produit pas automatiquement, mais la destruction en C++ non plus. Vous voyez souvent la fermeture des ressources dans le bloc finally.

3

Si vous écrivez une applet Java, vous pouvez remplacer la méthode "destroy ()" de l'applet. Il est...

 * Called by the browser or applet viewer to inform
 * this applet that it is being reclaimed and that it should destroy
 * any resources that it has allocated. The stop() method
 * will always be called before destroy().

Évidemment pas ce que vous voulez, mais pourrait être ce que les autres recherchent.

1
Dave

L'équivalent le plus proche d'un destructeur en Java est la méthode finalize () . La grande différence par rapport à un destructeur traditionnel est que vous ne pouvez pas savoir quand il sera appelé, car cela relève de la responsabilité du ramasse-miettes. Je vous recommande fortement de lire attentivement cette question avant de l'utiliser, car vos modèles RAIA typiques pour les descripteurs de fichiers, etc. ne fonctionneront pas de manière fiable avec finalize ().

1
Nik

Bien que la technologie GC de Java ait connu des avancées considérables, vous devez rester conscient de vos références. On pense à de nombreux cas de modèles de référence apparemment triviaux qui sont en réalité des nids de rats sous le capot. 

Dans votre message, il ne semble pas que vous essayiez de mettre en œuvre une méthode de réinitialisation à des fins de réutilisation d’objet (true?). Vos objets contiennent-ils un autre type de ressources qui doivent être nettoyées (c'est-à-dire des flux qui doivent être fermés, tout objet mis en pool ou emprunté qui doit être renvoyé)? Si la seule chose qui vous préoccupe est la mémoire dealloc, je reconsidérerais ma structure d'objet et essaierais de vérifier que mes objets sont des structures autonomes qui seront nettoyées au moment de la GC. 

0
tyler

Il n'y a pas exactement de classe destructrice en Java, la classe est détruite automatiquement en Java par Garbage Collector. mais vous pouvez le faire en utilisant ci-dessous un, mais ce n'est pas exactement la même chose:

finalize ()

Il y avait une question qui a engendré une discussion approfondie de finaliser , afin que vous puissiez avoir plus de profondeur si nécessaire ... 

0
Manas

Beaucoup de bonnes réponses ici, mais il y a quelques informations supplémentaires sur les raisons pour lesquelles vous devriez éviter d'utiliser finalize () .

Si la machine virtuelle Java se ferme en raison de System.exit() ou Runtime.getRuntime().exit(), les finaliseurs ne seront pas exécutés par défaut. De Javadoc pour Runtime.exit () :

La séquence d'arrêt de la machine virtuelle comprend deux phases. Au cours de la première phase, tous les points d'ancrage enregistrés enregistrés, le cas échéant, sont démarrés dans un ordre non spécifié et autorisés à s'exécuter simultanément jusqu'à leur achèvement. Dans la deuxième phase, tous les finaliseurs non invoqués sont exécutés si la finalisation à la sortie a été activée. Une fois que cela est fait, la machine virtuelle s'arrête.

Vous pouvez appeler System.runFinalization() mais cela ne fait que "tout mettre en oeuvre pour mener à bien toutes les finalisations en attente" - et non une garantie.

Il existe une méthode System.runFinalizersOnExit() , mais ne l'utilisez pas - c'est dangereux, obsolète depuis longtemps.

0
kaan

Je pense seulement à la question initiale ... qui, je pense que nous pouvons conclure de toutes les autres réponses apprises, ainsi que de l'essentiel de Bloch Effective Java , point 7, "Éviter les finalistes", cherche la solution à une question légitime d'une manière inappropriée pour le langage Java ...: 

... ne serait pas une solution assez évidente de faire ce que le PO veut réellement être de garder tous vos objets qui doivent être réinitialisés dans une sorte de "parc", auquel tous les autres objets non réinitialisables ne sont référencés d'objet d'accesseur ... 

Et ensuite, lorsque vous devez "réinitialiser", vous déconnectez le parc existant et en créez un nouveau: tout le réseau d'objets du parc est projeté à la dérive, pour ne jamais revenir et un jour pour être collecté par le GC.

Si certains de ces objets sont Closeable (ou non, mais ont une méthode close), vous pouvez les placer dans un Bag dans le parc dès leur création (et éventuellement leur ouverture), et le dernier acte de l'accesseur avant de couper le parc serait être de passer par tous les Closeables fermer les ...?

Le code ressemblerait probablement à quelque chose comme ça:

accessor.getPlaypen().closeCloseables();
accessor.setPlaypen( new Playpen() );

closeCloseables serait probablement une méthode de blocage, impliquant probablement un verrou (par exemple CountdownLatch), pour traiter (et attendre le cas échéant) toute Runnables/Callables dans tout thread spécifique à la Playpen à terminer, le cas échéant, en particulier dans le fil JavaFX .

0
mike rodent