web-dev-qa-db-de.com

Warum sollten Sie finalize () jemals implementieren?

Ich habe viele Rookie-Java-Fragen zu finalize () durchgelesen und finde es irgendwie verwirrend, dass niemand wirklich deutlich gemacht hat, dass finalize () ein unzuverlässiger Weg ist, Ressourcen zu bereinigen. Ich habe jemanden gesehen, der kommentiert, dass er Connections bereinigt, was wirklich unheimlich ist, da die einzige Möglichkeit, einer geschlossenen Verbindung nahe zu kommen, der Abschluss von try (catch) ist. 

Ich wurde nicht in CS ausgebildet, aber seit fast einem Jahrzehnt programmiere ich professionell in Java, und ich habe noch nie jemanden gesehen, der finalize () in einem Produktionssystem implementiert hat. Dies bedeutet noch immer nicht, dass es nicht verwendet wird oder dass die Leute, mit denen ich gearbeitet habe, es richtig gemacht haben.

Meine Frage ist also: Welche Anwendungsfälle gibt es für die Implementierung von finalize (), die über einen anderen Prozess oder eine andere Syntax innerhalb der Sprache nicht zuverlässiger gehandhabt werden kann? 

Bitte geben Sie bestimmte Szenarien oder Ihre Erfahrungen an, einfach das Wiederholen eines Java-Lehrbuchs oder die endgültige Verwendung der beabsichtigten Verwendung ist nicht ausreichend und ist nicht die Absicht dieser Frage.

344
Spencer Kormos

Sie können es als Anschlag für ein Objekt verwenden, das eine externe Ressource (Socket, Datei usw.) enthält. Implementieren Sie eine close()-Methode und dokumentieren Sie, dass sie aufgerufen werden muss.

Implementieren Sie finalize(), um die close()-Verarbeitung durchzuführen, wenn Sie feststellen, dass dies noch nicht geschehen ist. Vielleicht mit etwas, das an stderr abgeladen wurde, um darauf hinzuweisen, dass Sie nach einem fehlerhaften Anrufer aufräumen.

Es bietet zusätzliche Sicherheit in einer außergewöhnlichen/fehlerhaften Situation. Nicht jeder Anrufer wird jedes Mal den richtigen try {} finally {}-Kram erledigen. Schade, aber in den meisten Umgebungen wahr.

Ich stimme zu, dass es selten gebraucht wird. Und wie Kommentatoren darauf hinweisen, kommt es mit GC-Overhead. Verwenden Sie diese Option nur, wenn Sie in einer langlebigen App die Sicherheit "Gürtel und Hosenträger" benötigen.

Ich sehe das ab Java 9, Object.finalize() ist veraltet! Sie zeigen uns auf Java.lang.ref.Cleaner und Java.lang.ref.PhantomReference als Alternativen.

211
John M

finalize () ist ein Hinweis an die JVM, dass es möglicherweise nett wäre, Ihren Code zu einem nicht angegebenen Zeitpunkt auszuführen. Das ist gut, wenn Sie möchten, dass der Code auf mysteriöse Weise ausgeführt wird.

In Finalizern etwas Wesentliches zu tun (im Grunde nichts außer Protokollierung), ist in drei Situationen auch gut:

  • sie möchten wetten, dass andere finalisierte Objekte immer noch in einem Zustand sind, den der Rest Ihres Programms für gültig hält.
  • sie möchten allen Methoden aller Klassen, die über einen Finalizer verfügen, eine Menge Prüfcode hinzufügen, um sicherzustellen, dass sie sich nach dem Abschluss korrekt verhalten.
  • sie möchten versehentlich fertiggestellte Objekte wiederbeleben und viel Zeit darauf verwenden, herauszufinden, warum sie nicht funktionieren und/oder warum sie nicht endgültig abgeschlossen werden, wenn sie schließlich freigegeben werden.

Wenn Sie der Meinung sind, dass Sie finalize () benötigen, ist es manchmal eine Phantomreferenz, die Sie wirklich wollen (die in dem angegebenen Beispiel eine harte Referenz auf eine Verbindung enthalten könnte, die von ihrem Referenznamen verwendet wird, und diese nach der Warteschlange der Phantomreferenz schließen). Dies hat auch die Eigenschaft, dass es auf mysteriöse Weise niemals ausgeführt werden kann, aber zumindest keine Methoden für abgeschlossene Objekte aufrufen oder erneut aufrufen kann. Es ist also genau das Richtige für Situationen, in denen Sie diese Verbindung nicht unbedingt sauber schließen müssen, aber Sie möchten es gerne, und die Kunden Ihrer Klasse können oder wollen sich nicht selbst schließen (was eigentlich fair ist - Was ist der Sinn eines Müllsammlers, wenn Sie Schnittstellen entwerfen, die erfordern eine bestimmte Aktion vor der Abholung ausgeführt wird? Das bringt uns einfach in die Zeit von Malloc/free zurück.)

In anderen Fällen benötigen Sie die Ressource, die Sie Ihrer Meinung nach verwalten, um robuster zu sein. Warum müssen Sie beispielsweise diese Verbindung schließen? Es muss letztendlich auf einer Art E/A basieren, die vom System bereitgestellt wird (Socket, Datei, was auch immer). Warum können Sie sich also nicht darauf verlassen, dass das System es für Sie schließt, wenn die niedrigste Ressourcenebene vorhanden ist? Wenn der Server am anderen Ende zwingend verlangt, dass Sie die Verbindung sauber trennen, anstatt nur die Steckdose zu trennen, was passiert dann, wenn jemand über das Stromkabel des Computers stolpert, auf dem Ihr Code läuft, oder das dazwischenliegende Netzwerk ausfällt?

Haftungsausschluss: Ich habe in der Vergangenheit an einer JVM-Implementierung gearbeitet. Ich hasse Finalisierer.

159
Steve Jessop

Eine einfache Regel: Verwenden Sie niemals Finalizer. Allein die Tatsache, dass ein Objekt über einen Finalizer verfügt (unabhängig davon, welchen Code es ausführt) ist ausreichend, um einen erheblichen Overhead für die Garbage Collection zu verursachen.

Aus einem article von Brian Goetz:

Objekte mit Finalizern (diejenigen, die Eine nicht triviale Methode finalize () haben) haben im Vergleich zu .__ einen erheblichen Aufwand. Objekte ohne Finalizer und sollten sparsam eingesetzt werden. Finalisierbar Objekte sind beide langsamer zuzuordnen und langsamer zu sammeln. Bei der Zuteilung Zu diesem Zeitpunkt muss die JVM eine beliebige .__ registrieren. finalisierbare Objekte mit dem Müll collector und (zumindest in der HotSpot-JVM-Implementierung) finalisierbare Objekte müssen einem .__ folgen. langsamerer Zuweisungspfad als die meisten anderen Objekte. Ebenso finalisierbar Objekte sind auch langsamer zu sammeln. Es nimmt mindestens zwei Müllsammlungen Zyklen (im besten Fall) vor einem finalizeable Objekt kann zurückgefordert werden, und der Garbage Collector muss das tun zusätzliche Arbeit, um den Finalizer aufzurufen . Das Ergebnis ist mehr Zeit Objekte zuordnen und sammeln und mehr Druck auf den Müll collector, weil der Speicher von .__ verwendet wird. nicht erreichbare finalisierbare Objekte sind länger aufbewahrt. Kombinieren Sie das mit dem Tatsache, dass Finalizer nicht .__ sind. garantiert für jeden vorhersehbaren Zeitrahmen oder sogar überhaupt, und Sie können stellen Sie fest, dass es relativ wenige gibt Situationen, für die der Abschluss erfolgt das richtige Werkzeug zu verwenden.

53
Tom

Ich habe finalize im Produktionscode nur verwendet, um zu überprüfen, ob die Ressourcen eines bestimmten Objekts bereinigt wurden. Wenn nicht, protokollieren Sie eine sehr lautstarke Nachricht. Es hat nicht wirklich versucht, es selbst zu machen, es hat einfach viel geschrien, wenn es nicht richtig gemacht wurde. Es stellte sich als sehr nützlich heraus.

43
skaffman

Ich mache Java seit 1998 professionell und habe nie finalize () implementiert. Nicht einmal.

32
Paul Tomblin

Ich bin nicht sicher, was Sie daraus machen können, aber ...

[email protected] ~/jdk1.6.0_02/src/
$ find . -name "*.Java" | xargs grep "void finalize()" | wc -l
41

Ich denke, die Sonne hat einige Fälle gefunden, in denen (sie denken) es verwendet werden sollte. 

25
itsadok

Ich habe finalize einmal verwendet, um zu verstehen, welche Objekte freigegeben wurden. Sie können einige nette Spiele mit Statik, Referenzzählung und so weiter spielen - aber es war nur zur Analyse.

Die akzeptierte Antwort ist gut, ich wollte nur hinzufügen, dass es jetzt einen Weg gibt, die Funktionalität des Finalisierens zu haben, ohne sie überhaupt zu verwenden.

Schauen Sie sich die "Referenz" -Klassen an. Schwache Referenz usw.

Sie können sie verwenden, um einen Verweis auf alle Ihre Objekte zu behalten, aber dieser Verweis ALLEIN wird GC nicht anhalten. Das Schöne daran ist, dass Sie eine Methode aufrufen können, wenn sie gelöscht wird. Diese Methode kann garantiert werden.

Eine andere Sache, auf die Sie achten sollten. Jedes Mal, wenn Sie so etwas überall im Code sehen (nicht nur in finalize, sondern dort, wo Sie es am wahrscheinlichsten sehen werden):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

Es ist ein Zeichen, dass jemand nicht wusste, was er tat. "Aufräumen" wie dieses ist praktisch nie nötig. Wenn die Klasse GC ist, wird dies automatisch durchgeführt.

Wenn Sie einen solchen Code in einem Finalize finden, ist es garantiert, dass die Person, die ihn geschrieben hat, verwirrt war. 

Wenn es anderswo ist, könnte es sein, dass der Code ein gültiger Patch für ein fehlerhaftes Modell ist (eine Klasse bleibt lange in der Umgebung und aus irgendeinem Grund mussten die referenzierten Objekte manuell freigegeben werden, bevor das Objekt mit GC bewertet wird). Im Allgemeinen liegt es daran, dass jemand vergessen hat, einen Hörer oder etwas zu entfernen, und kann nicht herausfinden, warum sein Objekt nicht GC-Gütesiegel ist. Deshalb werden nur die Dinge gelöscht, auf die es sich bezieht, und die Schultern gezuckt und weggehen.

Es sollte niemals verwendet werden, um "Quicker" aufzuräumen. 

25
Bill K
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!");  
    }
}

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

ergebnis:

Das ist finalisieren

MyObject lebt noch!

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

So können Sie eine unerreichbare Instanz in der finalize-Methode erreichen.

21
Kiki

finalize kann hilfreich sein, um Ressourcenlecks zu erkennen. Wenn die Ressource geschlossen werden soll, aber nicht die Tatsache schreibt, dass sie nicht für eine Protokolldatei geschlossen wurde, und schließt sie. Auf diese Weise entfernen Sie das Ressourcenleck und geben Ihnen die Möglichkeit, zu erfahren, dass es aufgetreten ist, um es zu beheben.

Ich programmiere in Java seit 1.0 alpha 3 (1995) und muss noch finalize für irgendetwas überschreiben ...

9
TofuBeer

Sie sollten sich nicht auf finalize () verlassen, um Ihre Ressourcen für Sie zu bereinigen. finalize () wird erst ausgeführt, wenn die Klasse gesammelt wurde. Es ist viel besser, Ressourcen explizit freizugeben, wenn Sie mit deren Verwendung fertig sind.

6
Bill the Lizard

Um einen Punkt in den obigen Antworten hervorzuheben: Finalizer werden auf dem einzigen GC-Thread ausgeführt. Ich habe von einer großen Sun-Demo gehört, bei der die Entwickler einigen Finalizern ein wenig Schlaf hinzugefügt und absichtlich eine ansonsten schicke 3D-Demo in die Knie gezwungen haben.

Am besten zu vermeiden, mit Ausnahme der test-env-Diagnose.

Eckels Denken in Java hat einen guten Abschnitt dazu.

5
Michael Easter

Sei vorsichtig, was du in einem finalize () machst. Besonders, wenn Sie es für Dinge wie den Aufruf von close () verwenden, um sicherzustellen, dass Ressourcen bereinigt werden. Es gab mehrere Situationen, in denen JNI-Bibliotheken mit dem ausgeführten Java-Code verknüpft waren, und unter allen Umständen, unter denen wir finalize () zum Aufrufen von JNI-Methoden verwendeten, würden wir sehr schlechte Java-Heap-Beschädigungen erhalten. Die Beschädigung wurde nicht durch den zugrunde liegenden JNI-Code selbst verursacht. Alle Speicherverfolgungen waren in den nativen Bibliotheken einwandfrei. Es war nur die Tatsache, dass wir überhaupt JNI-Methoden aus finalize () aufgerufen haben.

Dies war mit einem JDK 1.5, das noch weit verbreitet ist.

Wir haben erst viel später herausgefunden, dass etwas schiefgegangen ist, aber am Ende war immer die Methode finalize (), die JNI-Aufrufe verwendete.

3

Hmmm, ich habe es einmal benutzt, um Objekte zu bereinigen, die nicht in einen vorhandenen Pool zurückgegeben wurden. Sie wurden viel herumgereicht, daher war es unmöglich zu sagen, wann sie sicher in den Pool zurückkehren konnten. Das Problem bestand darin, dass es bei der Müllsammlung eine enorme Strafe verursachte, die weitaus größer war als alle Einsparungen durch das Sammeln der Objekte. Es war ungefähr einen Monat in Produktion, bevor ich den ganzen Pool herausgerissen hatte, alles dynamisch machte und damit fertig war.

3
patros

Wenn Sie Code schreiben, der von anderen Entwicklern verwendet wird, müssen Sie eine Art "Cleanup" -Methode aufrufen, um Ressourcen freizugeben. Manchmal vergessen die anderen Entwickler, Ihre Cleanup- (oder Close- oder Destroy-Methode oder was auch immer) aufzurufen. Um mögliche Ressourcenlecks zu vermeiden, können Sie in der finalize-Methode überprüfen, ob die Methode aufgerufen wurde, und wenn nicht, können Sie sie selbst aufrufen. 

Viele Datenbanktreiber tun dies in ihren Statement- und Connection-Implementierungen, um ein wenig Sicherheit gegen Entwickler zu bieten, die vergessen, sie in der Nähe aufzurufen.

3
John Meagher

Edit: Okay, es funktioniert wirklich nicht. Ich habe es implementiert und dachte, wenn es fehlschlägt, ist das für mich in Ordnung, aber es wurde nicht einmal die Methode finalize aufgerufen.

Ich bin kein professioneller Programmierer, aber in meinem Programm habe ich einen Fall, den ich für ein gutes Beispiel für finalize () halte, einen Cache, der seinen Inhalt auf die Festplatte schreibt, bevor er zerstört wird. Da es nicht unbedingt erforderlich ist, dass es jedes Mal bei einer Zerstörung ausgeführt wird, beschleunigt es nur mein Programm, ich hoffe, dass ich es nicht falsch gemacht habe.

@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);
}
2
user265243

Es kann praktisch sein, Dinge zu entfernen, die an einem globalen/statischen Ort hinzugefügt wurden (aus der Not) und beim Entfernen des Objekts entfernt werden müssen. Zum Beispiel:

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

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

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

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

Als Anmerkung:

Ein Objekt, das finalize () überschreibt, wird speziell vom Speicherbereiniger behandelt. Normalerweise wird ein Objekt während des Erfassungszyklus sofort zerstört, nachdem sich das Objekt nicht mehr im Gültigkeitsbereich befindet. Finalisierbare Objekte werden jedoch stattdessen in eine Warteschlange verschoben, wo separate Finalisierungsthreads die Warteschlange entleeren und die Methode finalize () für jedes Objekt ausführen. Wenn die finalize () -Methode beendet ist, ist das Objekt im nächsten Zyklus schließlich für die Garbage Collection bereit.

finalize veraltet auf Java-9

0
Sudip Bhandari

iirc - Sie können die finalize-Methode als Mittel zur Implementierung eines Pooling-Mechanismus für teure Ressourcen verwenden - damit sie auch keine GC erhalten.

0
JGFMK

Ich persönlich verwende finalize() fast nie, außer in einem seltenen Fall: Ich habe eine benutzerdefinierte generische Sammlung erstellt und eine benutzerdefinierte finalize()-Methode geschrieben, die Folgendes ausführt:

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 ist eine von mir erstellte Schnittstelle, mit der Sie angeben können, dass Sie selten implementierte Object-Methoden wie #finalize(), #hashCode() und #clone() implementiert haben.)

Bei Verwendung einer Schwestermethode #setDestructivelyFinalizes(boolean) kann das Programm, das meine Auflistung verwendet, (Hilfe) garantieren, dass durch das Zerstören eines Verweises auf diese Auflistung auch Verweise auf den Inhalt zerstört werden und alle Fenster verfügbar werden, die die JVM möglicherweise unbeabsichtigt am Leben halten. Ich habe mir auch überlegt, irgendwelche Threads zu stoppen, aber das hat eine ganze neue Dose Würmer geöffnet.

0
Supuhstar

Die Ressourcen (Datei, Socket, Stream usw.) müssen geschlossen werden, sobald ihre Verwendung abgeschlossen ist. Sie haben im Allgemeinen die close()-Methode, die wir im Allgemeinen im Abschnitt finally der try-catch-Anweisungen aufrufen. Manchmal kann finalize() auch von wenigen Entwicklern verwendet werden, aber IMO ist keine geeignete Methode, da es keine Garantie dafür gibt, dass finalize immer aufgerufen wird.

In Java 7 haben wir try-with-resources Anweisung, die wie folgt verwendet werden kann:

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.
}

In dem obigen Beispiel schließt try-with-resource die Ressource BufferedReader automatisch durch Aufrufen der close()-Methode. Wenn wir wollen, können wir auch Closeable in unseren eigenen Klassen implementieren und auf ähnliche Weise verwenden. IMO scheint es ordentlicher und einfacher zu verstehen.

0
i_am_zero

Die akzeptierten Antwortlisten, mit denen eine Ressource während des Finalisierens geschlossen wird, können ausgeführt werden.

diese Antwort zeigt jedoch, dass Sie zumindest in Java8 mit dem JIT-Compiler unerwartete Probleme haben, bei denen der Finalizer manchmal sogar aufgerufen wird, bevor Sie das Lesen aus einem von Ihrem Objekt verwalteten Stream beenden.

Selbst in dieser Situation wäre der Aufruf von finalize nicht zu empfehlen.

0
Joeblade