webentwicklung-frage-antwort-db.com.de

Gibt es einen Destruktor für Java?

Gibt es einen Destruktor für Java? Ich kann anscheinend keine Dokumentation dazu finden. Wenn nicht, wie kann ich dieselbe Wirkung erzielen?

Um meine Frage genauer zu fassen, schreibe ich eine Anwendung, die sich mit Daten befasst, und die Spezifikation besagt, dass es eine 'Zurücksetzen'-Schaltfläche geben sollte, die die Anwendung in ihren ursprünglichen Status zurückbringt. Alle Daten müssen jedoch "live" sein, es sei denn, die Anwendung wird geschlossen oder die Reset-Taste gedrückt.

Da ich in der Regel ein C/C++ - Programmierer bin, dachte ich, dass dies einfach zu implementieren wäre. (Und deshalb habe ich geplant, es als letztes zu implementieren.) Ich habe mein Programm so strukturiert, dass sich alle 'resetfähigen' Objekte in derselben Klasse befinden, sodass ich einfach alle 'Live'-Objekte zerstören kann, wenn eine Reset-Taste gedrückt wird.

Ich dachte, wenn ich nur die Daten dereferenzieren und warten würde, bis der Garbage Collector sie sammelt, würde es nicht zu einem Speicherleck kommen, wenn mein Benutzer wiederholt Daten eingibt und die Reset-Taste drückt? Ich habe auch gedacht, da Java als Sprache recht ausgereift ist, sollte es einen Weg geben, dies zu verhindern oder dies grausam anzugehen.

530
wai

Da Java eine nicht erfasste Sprache ist, können Sie nicht vorhersagen, wann (oder sogar wann) ein Objekt zerstört wird. Daher gibt es kein direktes Äquivalent eines Destruktors.

Es gibt eine vererbte Methode mit dem Namen finalize, die jedoch ausschließlich nach Ermessen des Garbage Collectors aufgerufen wird. Für Klassen, die explizit aufräumen müssen, besteht die Konvention darin, eine close -Methode zu definieren und finalize nur für die Überprüfung der Integrität zu verwenden (d. H. Wenn close nicht aufgerufen wurde, tun Sie es jetzt und protokollieren Sie einen Fehler).

Es gab eine Frage, die in letzter Zeit zu einer ausführlichen Diskussion über finalize geführt hat , so dass bei Bedarf mehr Tiefe geboten werden sollte ...

479
Garth Gilmour

Wenn Sie Java 7 verwenden, werfen Sie einen Blick auf die Anweisung try-with-resources . Zum Beispiel:

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

Hier wird die nicht mehr benötigte Ressource in der BufferedReader.close()-Methode freigegeben. Sie können eine eigene Klasse erstellen, die AutoCloseable implementiert und auf ähnliche Weise verwendet. 

Diese Anweisung ist in Bezug auf die Codestrukturierung begrenzter als finalize, macht aber gleichzeitig den Code einfacher zu verstehen und zu verwalten. Es gibt auch keine Garantie dafür, dass eine finalize-Methode während der Lebenszeit der Anwendung überhaupt aufgerufen wird.

108

Nein, keine Zerstörer hier. Der Grund ist, dass alle Java-Objekte Heap zugewiesen und Müll gesammelt wird. Ohne explizite Freigabe (d. H. Den Löschoperator von C++) gibt es keine sinnvolle Möglichkeit, echte Destruktoren zu implementieren.

Java unterstützt Finalizer, sie sind jedoch nur als Sicherung für Objekte gedacht, die einen Handle für native Ressourcen wie Sockets, Dateihandles, Fensterhandles usw. enthalten. Wenn der Garbage Collector ein Objekt ohne Finalizer erfasst, markiert er einfach den Speicher Region als frei und das ist es. Wenn das Objekt über einen Finalizer verfügt, wird es zuerst in einen temporären Speicherort kopiert (denken Sie daran, wir sammeln hier Müll), dann wird es in eine Warteschlange mit Warteschlangen eingereiht, die noch nicht abgeschlossen ist, und dann fragt ein Finalizer-Thread die Warteschlange mit sehr niedriger Priorität ab und führt den Finalizer aus.

Wenn die Anwendung beendet wird, stoppt die JVM, ohne auf die Fertigstellung der ausstehenden Objekte zu warten. Daher gibt es praktisch keine Garantie dafür, dass Ihre Finalizer jemals ausgeführt werden.

107
ddimitrov

Die Verwendung von finalize () - Methoden sollte vermieden werden. Sie sind kein zuverlässiger Mechanismus für das Aufräumen von Ressourcen und es ist möglich, Probleme mit dem Garbage Collector zu verursachen, indem sie missbraucht werden.

Wenn Sie in Ihrem Objekt einen Deallocation-Aufruf benötigen, um Ressourcen freizugeben, verwenden Sie einen expliziten Methodenaufruf. Diese Konvention ist in vorhandenen APIs zu sehen (z. B. Closeable , Graphics.dispose () , Widget.dispose () ) und wird normalerweise über try/finally aufgerufen .

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

Versuche, ein abgelegtes Objekt zu verwenden, sollten eine Laufzeitausnahme auslösen (siehe IllegalStateException ).


BEARBEITEN:

Ich dachte, wenn alles was ich tat, nur die Daten dereferenzieren und auf .__ warten. der Müllsammler, um sie zu sammeln, wäre da kein Speicherleck, wenn mein Benutzer hat wiederholt Daten eingegeben und die Reset-Taste gedrückt?

In der Regel müssen Sie lediglich die Objekte dereferenzieren - zumindest so sollte es funktionieren. Wenn Sie sich Sorgen um die Garbage Collection machen, besuchen Sie Java SE 6 HotSpot [tm] Virtual Machine Garbage Collection-Tuning (oder das entsprechende Dokument für Ihre JVM-Version).

25
McDowell

Mit der Freigabe von Java 1.7 haben Sie jetzt die zusätzliche Möglichkeit, den try-with-resources-Block zu verwenden. Zum Beispiel, 

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..."); 
        } 
    }
}

Wenn Sie diese Klasse ausführen, wird c.close() ausgeführt, wenn der try-Block verlassen wird und bevor die catch- und finally-Blöcke ausgeführt werden. Anders als bei der finalize()-Methode wird garantiert, dass close() ausgeführt wird. Es ist jedoch nicht erforderlich, es explizit in der finally-Klausel auszuführen.

22
waku

Ich stimme völlig anderen Antworten zu und sage, dass ich mich nicht auf die Ausführung von finalize verlassen darf.

Zusätzlich zu try-catch-finally-Blöcken können Sie Runtime # addShutdownHook (in Java 1.6 eingeführt) verwenden, um die endgültige Bereinigung Ihres Programms durchzuführen. 

Dies ist nicht dasselbe wie Destruktoren, aber es kann ein Shutdown-Hook implementiert werden, der Listener-Objekte registriert, für die Bereinigungsmethoden (persistente Datenbankverbindungen schließen, Dateisperren entfernen usw.) aufgerufen werden können das würde normalerweise in Destruktoren ausgeführt werden. Wiederum - dies ist kein Ersatz für Konstruktoren, aber in einigen Fällen können Sie die gewünschte Funktionalität damit ansprechen.

Der Vorteil davon ist, dass das Dekonstruktionsverhalten lose gekoppelt vom Rest Ihres Programms ist.

13
Markus Lux

Nein, Java.lang.Object#finalize ist der nächstgelegene, den Sie bekommen können.

Wann (und ob) es aufgerufen wird, ist jedoch nicht garantiert.
Siehe: Java.lang.Runtime#runFinalizersOnExit(boolean)

9
flicken

Es tut mir leid, wenn dies vom Hauptthema abweicht, aber in der Dokumentation zu Java.util.Timer (SE6) heißt es:

"Nachdem der letzte Live-Verweis auf ein Timer-Objekt nicht mehr vorhanden ist und alle ausstehenden Aufgaben abgeschlossen wurden, wird der Task-Ausführungsthread des Zeitgebers ordnungsgemäß beendet (und unterliegt der Speicherbereinigung). Dies kann jedoch beliebig lange dauern Der Taskausführungsthread wird nicht als Daemon-Thread ausgeführt und kann daher verhindern, dass eine Anwendung beendet wird. Wenn ein Aufrufer den Taskausführungsthread eines Timers schnell beenden möchte, sollte der Aufrufer die Abbruchmethode des Timers aufrufen ... " 

Ich möchte abbrechen, wenn die Klasse, die den Timer besitzt, ihre letzte Referenz (oder unmittelbar danach) verliert. Hier könnte ein zuverlässiger Destruktor das für mich tun. Die obigen Kommentare zeigen, dass die Entscheidung letztendlich schlecht ist, aber gibt es eine elegante Lösung? Dieses Geschäft von "..., das verhindern kann, dass eine Anwendung beendet wird ..." ist nicht ansprechend.

6

Beachten Sie zunächst, dass, da Java Müll gesammelt wird, selten etwas zur Objektzerstörung benötigt wird. Erstens, weil Sie normalerweise keine verwalteten Ressourcen zur Verfügung haben, und zweitens, weil Sie nicht vorhersagen können, wann oder ob dies geschehen wird. Daher ist es unpassend für Dinge, die Sie ausführen müssen, sobald mein Objekt nicht mehr verwendet wird ".

Sie können benachrichtigt werden, nachdem ein Objekt mithilfe von Java.lang.ref.PhantomReference zerstört wurde. Die Angabe, dass das Objekt zerstört wurde, ist möglicherweise etwas ungenau. Wenn jedoch eine Phantomreferenz darauf in die Warteschlange gestellt wird, kann sie nicht mehr wiederhergestellt werden das gleiche). Eine häufige Verwendung ist:

  • Trennen Sie die Ressource (n) in Ihrer Klasse, die zerstört werden müssen, in ein anderes Hilfsobjekt (beachten Sie: Wenn Sie lediglich eine Verbindung schließen, was ein üblicher Fall ist, müssen Sie keine neue Klasse schreiben: die zu schließende Verbindung wäre in diesem Fall das "Hilfsobjekt".
  • Wenn Sie Ihr Hauptobjekt erstellen, erstellen Sie auch eine PhantomReference darauf. Lassen Sie dies entweder auf das neue Hilfsobjekt verweisen oder richten Sie eine Zuordnung von PhantomReference-Objekten zu den entsprechenden Hilfsobjekten ein.
  • Nachdem das Hauptobjekt gesammelt wurde, wird die PhantomReference in eine Warteschlange gestellt (oder eher in eine Warteschlange gestellt - wie Finalizer gibt es keine Garantie, dass dies jemals der Fall sein wird, wenn beispielsweise VM beendet wird. Stellen Sie sicher, dass Sie die Warteschlange bearbeiten (entweder in einem speziellen Thread oder von Zeit zu Zeit). Aufgrund des harten Verweises auf das Hilfsobjekt wurde das Hilfsobjekt noch nicht erfasst. Machen Sie also eine beliebige Bereinigung des Helferobjekts, verwerfen Sie dann die PhantomReference und der Helfer wird schließlich auch abgeholt.

Es gibt auch finalize (), das wie ein Destruktor aussieht, sich aber nicht so verhält. Es ist normalerweise keine gute Option.

6
Steve Jessop

Ich stimme den meisten Antworten zu.

Sie sollten sich nicht vollständig auf finalize oder ShutdownHook verlassen.

abschließen

  1. JVM garantiert nicht, wann diese finalize()-Methode aufgerufen wird.

  2. finalize() wird nur einmal vom GC-Thread aufgerufen, wenn sich das Objekt von der Finalisierungsmethode wiederbelebt, und Finalize nicht erneut aufgerufen wird.

  3. In Ihrer Anwendung haben Sie möglicherweise einige Live-Objekte, auf denen die Garbage Collection niemals aufgerufen wird.

  4. Alle Ausnahmen, die von der Finalisierungsmethode ausgelöst werden, werden vom GC-Thread ignoriert

  5. System.runFinalization(true)- und Runtime.getRuntime().runFinalization(true)-Methoden erhöhen die Wahrscheinlichkeit, dass die finalize()-Methode aufgerufen wird, aber nun sind diese beiden Methoden veraltet. Diese Methoden sind sehr gefährlich, da keine Gewindesicherheit besteht und möglicherweise Deadlocks entstehen. 

shutdownHooks

public void addShutdownHook(Thread hook)

Registriert einen neuen Hook zum Herunterfahren der virtuellen Maschine.

Die Java Virtual Machine wird als Reaktion auf zwei Arten von Ereignissen heruntergefahren:

  1. Das Programm wird normal beendet, wenn der letzte Nicht-Daemon-Thread beendet wird oder wenn die exit-Methode (äquivalent System.exit) aufgerufen wird, oder
  2. Die virtuelle Maschine wird als Reaktion auf eine Benutzerunterbrechung (z. B. Eingabe von ^ C) oder ein systemweites Ereignis (z. B. Benutzerabmeldung oder Herunterfahren des Systems) beendet.
  3. Ein Shutdown-Hook ist einfach ein initialisierter, aber nicht gestarteter Thread. Wenn die virtuelle Maschine mit der Herunterfahren-Sequenz beginnt, startet sie alle registrierten Shutdown-Hooks in einer nicht festgelegten Reihenfolge und lässt sie gleichzeitig laufen. Wenn alle Hooks abgeschlossen sind, werden alle unbeaufsichtigten Finalizer ausgeführt, wenn die Finalisierung beim Beenden aktiviert wurde. 
  4. Schließlich wird die virtuelle Maschine angehalten. Beachten Sie, dass Daemon-Threads während der Shutdown-Sequenz weiterhin ausgeführt werden. Dies gilt auch für Nicht-Daemon-Threads, wenn das Shutdown durch Aufrufen der Exit-Methode initiiert wurde.
  5. Abschalthaken sollten auch ihre Arbeit schnell beenden. Wenn ein Programm exit aufruft, wird erwartet, dass die virtuelle Maschine sofort heruntergefahren und beendet wird.

    Aber auch die Oracle-Dokumentation zitierte dies 

In seltenen Fällen kann es vorkommen, dass die virtuelle Maschine abbricht, dh nicht mehr ohne sauberes Herunterfahren ausgeführt wird

Dies geschieht, wenn die virtuelle Maschine extern beendet wird, z. B. mit dem Signal SIGKILL unter Unix oder dem Aufruf TerminateProcess unter Microsoft Windows. Die virtuelle Maschine kann auch abbrechen, wenn eine native Methode ausfällt, indem beispielsweise interne Datenstrukturen beschädigt werden oder versucht wird, auf nicht vorhandenen Speicher zuzugreifen. Wenn die virtuelle Maschine abbricht, kann keine Garantie dafür übernommen werden, ob Shutdown-Hooks ausgeführt werden.

Schlussfolgerung: Verwenden Sie try{} catch{} finally{}-Blöcke ordnungsgemäß und geben Sie kritische Ressourcen im finally(}-Block frei. Bei der Freigabe von Ressourcen im finally{}-Block fangen Sie Exception und Throwable.

4
Ravindra babu

Wenn es nur um die Erinnerung geht, machen Sie sich keine Sorgen. Vertrauen Sie einfach dem GC, dass er gute Arbeit leistet. Ich habe tatsächlich gesehen, dass etwas so effizient ist, dass es für die Leistung besser sein könnte, viele kleine Objekte zu erstellen, als in einigen Fällen große Arrays zu verwenden.

4
John Nilsson

Die Funktion finalize() ist der Destruktor.

Es sollte jedoch normalerweise nicht verwendet werden, da es nach dem GC aufgerufen wird und Sie nicht sagen können, wann dies geschehen wird (falls überhaupt).

Darüber hinaus sind mehr als ein GC erforderlich, um Objekte mit finalize() freizugeben.

Sie sollten versuchen, an den logischen Stellen in Ihrem Code mit den try{...} finally{...}-Anweisungen zu bereinigen!

4
Shimi Bandiel

Vielleicht können Sie einen try ... finally-Block verwenden, um das Objekt im Kontrollfluss abzuschließen, bei dem Sie das Objekt verwenden. Natürlich geschieht das nicht automatisch, aber auch nicht in C++. Sie sehen oft das Schließen von Ressourcen im finally-Block.

3

In Lombok gibt es eine Annotation @ Cleanup , die hauptsächlich C++ - Destruktoren ähnelt:

@Cleanup
ResourceClass resource = new ResourceClass();

Während der Verarbeitung (zur Kompilierungszeit) fügt Lombok den entsprechenden try-finally - Block ein, so dass resource.close() aufgerufen wird, wenn die Ausführung den Gültigkeitsbereich der Variablen verlässt. Sie können auch explizit eine andere Methode zum Freigeben der Ressource angeben, z. resource.dispose():

@Cleanup("dispose")
ResourceClass resource = new ResourceClass();
2
Alexey

Wenn Sie ein Java-Applet schreiben, können Sie die Applet-Methode "destroy ()" überschreiben. Es ist...

 * 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().

Offensichtlich nicht, was Sie wollen, aber vielleicht das, was andere Leute suchen.

1
Dave

Die Entsprechung zu einem Destruktor in Java ist die Methode finalize () . Der große Unterschied zu einem herkömmlichen Destruktor besteht darin, dass Sie nicht sicher sein können, wann er aufgerufen wird, da dies die Aufgabe des Müllsammlers ist. Ich würde nachdrücklich empfehlen, dies sorgfältig zu lesen, bevor Sie es verwenden, da Ihre typischen RAIA-Muster für Dateihandles usw. mit finalize () nicht zuverlässig funktionieren.

1
Nik

Obwohl die GC-Technologie von Java beträchtliche Fortschritte gemacht hat, müssen Sie immer auf Ihre Referenzen achten. Man denke an zahlreiche Fälle scheinbar trivialer Referenzmuster, bei denen es sich tatsächlich um Rattennester unter der Haube handelt. 

In Ihrem Beitrag klingt es nicht so, als würden Sie versuchen, eine Rücksetzmethode für die Wiederverwendung von Objekten zu implementieren (true?). Enthalten Ihre Objekte andere Arten von Ressourcen, die bereinigt werden müssen (z. B. Streams, die geschlossen werden müssen, gepoolte oder ausgeliehene Objekte, die zurückgegeben werden müssen)? Wenn das einzige, worüber Sie sich Sorgen machen, der Speicher-Dealloc ist, würde ich meine Objektstruktur überdenken und versuchen, zu überprüfen, ob meine Objekte eigenständige Strukturen sind, die zur GC-Zeit aufgeräumt werden. 

0
tyler

Es gibt keine exakte Destruktor-Klasse in Java, die Klasse wird in Java automatisch vom Garbage Collector zerstört. Aber Sie könnten das unter einem tun, aber es ist nicht genau dasselbe:

finalize ()

Es gab eine Frage, die eine ausführliche Diskussion über finalize hervorbrachte , so dass Sie bei Bedarf mehr Tiefe bekommen sollten ... 

0
Manas

Viele gute Antworten hier, aber es gibt einige zusätzliche Informationen darüber, warum Sie finalize () vermeiden sollten.

Wenn die JVM aufgrund von System.exit() oder Runtime.getRuntime().exit() beendet wird, werden Finalizer nicht standardmäßig ausgeführt. From Javadoc für Runtime.exit () :

Die Sequenz zum Herunterfahren der virtuellen Maschine besteht aus zwei Phasen. In der ersten Phase werden alle registrierten Shutdown-Hooks, sofern vorhanden, in einer nicht festgelegten Reihenfolge gestartet und können gleichzeitig ausgeführt werden, bis sie beendet sind. In der zweiten Phase werden alle nicht aufgerufenen Finalizer ausgeführt, wenn die Finalisierung beim Beenden aktiviert wurde. Sobald dies erledigt ist, stoppt die virtuelle Maschine.

Sie können System.runFinalization() anrufen, aber es wird nur "die größte Anstrengung unternommen, um alle ausstehenden Finalisierungen abzuschließen" - keine Garantie.

Es gibt eine System.runFinalizersOnExit() -Methode, aber verwenden Sie sie nicht - sie ist unsicher und längst veraltet.

0
kaan

Ich denke nur an die ursprüngliche Frage ... die, ich denke, wir können aus allen anderen gelernten Antworten schließen, und auch aus Blochs grundlegendem Effective Java , Punkt 7, "Finalizer vermeiden", sucht die Lösung einer legitimen Frage in einer für die Java-Sprache ungeeigneten Weise ...: 

... wäre keine nahe liegende Lösung, um das zu tun, was das OP eigentlich will, um alle Ihre Objekte, die zurückgesetzt werden müssen, in einer Art "Laufstall" zu halten, auf den alle anderen nicht rücksetzbaren Objekte nur durch eine Art Verweise verweisen von Accessor Objekt ... 

Und dann, wenn Sie "zurücksetzen" müssen, trennen Sie den vorhandenen Laufstall und erstellen einen neuen: Das gesamte Netz von Objekten im Laufstall wird verschoben, kehrt nie zurück und ein Tag wird vom GC abgeholt.

Wenn eines dieser Objekte Closeable ist (oder nicht, jedoch über eine close-Methode verfügt), könnten Sie sie in ein Bag-Objekt im Laufstall einfügen, wenn sie erstellt (und möglicherweise geöffnet) werden, und der letzte Vorgang des Zugriffselements vor dem Abschneiden des Laufbereichs wäre Seien Sie durch alle Closeables gehen, die sie schließen ...?

Der Code würde wahrscheinlich so aussehen:

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

closeCloseables wäre wahrscheinlich eine Blockierungsmethode, die wahrscheinlich ein Latch beinhaltet (z. B. CountdownLatch), um mit jedem Runnables/Callables in beliebigen Threads, die speziell für den Playpen spezifisch sind, umzugehen (und entsprechend zu warten), insbesondere im JavaFX-Thread .

0
mike rodent