Die beiden Hauptargumente gegen das Überschreiben Object.finalize()
sind:
Sie können sich nicht entscheiden, wann es heißt.
Es kann überhaupt nicht aufgerufen werden.
Wenn ich das richtig verstehe, denke ich nicht, dass das gute Gründe genug sind, um Object.finalize()
so viel zu hassen .
Es ist Sache der VM-Implementierung und des GC, zu bestimmen, wann der richtige Zeitpunkt für die Freigabe eines Objekts ist, nicht der Entwickler. Warum ist es wichtig zu entscheiden, wann
Object.finalize()
angerufen wird?Normalerweise, und korrigieren Sie mich, wenn ich falsch liege, wird das einzige Mal
Object.finalize()
, wenn die Anwendung beendet wird, bevor der GC die Möglichkeit hat, ausgeführt zu werden, nicht aufgerufen. Das Objekt wurde jedoch ohnehin freigegeben, als der Prozess der Anwendung damit beendet wurde. AlsoObject.finalize()
wurde nicht angerufen, weil es nicht nötig war, angerufen zu werden. Warum sollte sich der Entwickler darum kümmern?
Jedes Mal, wenn ich Objekte verwende, die ich manuell schließen muss (z. B. Dateizugriffsnummern und Verbindungen), bin ich sehr frustriert. Ich muss ständig prüfen, ob ein Objekt eine Implementierung von hat close()
, und ich bin mir sicher, dass ich in der Vergangenheit einige Aufrufe verpasst habe. Warum ist es nicht einfacher und sicherer, es der VM und dem GC zu überlassen, diese Objekte zu entsorgen, indem Sie die close()
Implementierung einfügen Object.finalize()
?
quelle
finalize()
ein bisschen durcheinander. Wenn Sie es jemals implementieren, stellen Sie sicher, dass es in Bezug auf alle anderen Methoden für dasselbe Objekt threadsicher ist.Antworten:
Nach meiner Erfahrung gibt es nur einen Grund für das Überschreiben
Object.finalize()
, aber es ist ein sehr guter Grund :Die statische Analyse kann nur Auslassungen in Szenarien mit geringer Auslastung erkennen, und die Compiler-Warnungen, die in einer anderen Antwort erwähnt werden, enthalten eine so vereinfachte Sicht der Dinge, dass Sie sie tatsächlich deaktivieren müssen, um alles zu tun, was nicht trivial ist. (Ich habe weit mehr Warnungen aktiviert als jeder andere Programmierer, den ich kenne oder von dem ich je gehört habe, aber ich habe keine dummen Warnungen aktiviert.)
Die Finalisierung scheint ein guter Mechanismus zu sein, um sicherzustellen, dass die Ressourcen nicht ungenutzt bleiben. Die meisten Menschen sehen dies jedoch völlig falsch: Sie sehen darin einen alternativen Fallback-Mechanismus, eine "zweite Chance", die die Ressourcen automatisch schützt Tag durch Entsorgung der Ressourcen, die sie vergessen haben. Das ist absolut falsch . Es muss nur eine Möglichkeit geben, etwas zu tun: Entweder schließen Sie immer alles, oder die Finalisierung schließt immer alles. Da die Finalisierung jedoch unzuverlässig ist, kann es nicht die Finalisierung sein.
Es gibt also dieses Schema, das ich Mandatory Disposal nenne , und das besagt, dass der Programmierer dafür verantwortlich ist, immer explizit alles zu schließen, was
Closeable
oder implementiertAutoCloseable
. (Die Anweisung "try-with-resources" gilt immer noch als explizites Schließen.) Natürlich kann der Programmierer dies vergessen. Hier kommt also die Finalisierung ins Spiel, aber nicht als magische Fee, die die Dinge am Ende auf magische Weise richtig macht: Wenn die Finalisierung entdeckt wird dasclose()
wurde nicht aufgerufen, tut es nichtversuchen, es aufzurufen, gerade weil es (mit mathematischer Sicherheit) Horden von n00b-Programmierern geben wird, die sich darauf verlassen werden, dass es die Arbeit erledigt, für die sie zu faul oder zu abwesend waren. Wenn Finalization bei der obligatorischen Entsorgung feststellt, dass diese Funktionclose()
nicht aufgerufen wurde, wird eine leuchtend rote Fehlermeldung protokolliert, die dem Programmierer mit großen, fetten Großbuchstaben mitteilt, er solle sein Problem beheben.Als zusätzlichen Vorteil geht das Gerücht aus, dass "die JVM eine triviale finalize () -Methode ignorieren wird (z. B. eine, die nur zurückkehrt, ohne etwas zu tun, wie es in der Object-Klasse definiert ist)", sodass Sie bei obligatorischer Entsorgung jegliche Finalisierung vermeiden können Overhead in Ihrem gesamten System ( Informationen darüber, wie schrecklich dieser Overhead ist, finden Sie in der Antwort von alip ), indem Sie Ihre
finalize()
Methode wie folgt codieren :Die Idee dahinter ist, dass
Global.DEBUG
es sich um einestatic final
Variable handelt, deren Wert zum Kompilierungszeitpunkt bekannt ist. Wenn diesfalse
der Fall ist, gibt der Compiler überhaupt keinen Code für die gesamteif
Anweisung aus, wodurch dies zu einem trivialen (leeren) Finalizer wird, der wiederum bedeutet, dass Ihre Klasse so behandelt wird, als hätte sie keinen Finalizer. (In C # würde dies mit einem netten#if DEBUG
Block geschehen , aber was können wir tun, das ist Java, wo wir anscheinend Einfachheit im Code mit zusätzlichem Overhead im Gehirn bezahlen.)Weitere Informationen zur Entsorgungspflicht mit zusätzlichen Erläuterungen zur Entsorgung von Ressourcen in dot Net finden Sie hier: michael.gr: Entsorgungspflicht vs.
quelle
close()
für alle Fälle. Ich glaube, wenn meine Tests nicht vertrauenswürdig sind, sollte ich das System besser nicht für die Produktion freigeben.if( Global.DEBUG && ...
Konstrukt funktioniert und die JVM diefinalize()
Methode als trivial ignoriert ,Global.DEBUG
muss @Mike zum Zeitpunkt der Kompilierung festgelegt werden (im Gegensatz zu injiziertem Code usw.). Der Aufrufsuper.finalize()
außerhalb des if-Blocks ist auch für die JVM ausreichend, um ihn als nicht trivial zu behandeln (unabhängig vom Wert vonGlobal.DEBUG
, zumindest in HotSpot 1.8), selbst wenn auch der Aufruf der Superklasse#finalize()
trivial ist!java.io
. Wenn diese Art von Thread-Sicherheit nicht auf der Wunschliste stand, wird der durchfinalize()
…Da Datei-Handles und -Verbindungen (d. H. Dateideskriptoren auf Linux- und POSIX-Systemen) eine recht knappe Ressource sind (auf einigen Systemen sind sie möglicherweise auf 256 oder auf anderen auf 16384 beschränkt; siehe setrlimit (2) ). Es gibt keine Garantie dafür, dass der GC häufig genug (oder zum richtigen Zeitpunkt) angerufen wird, um die Erschöpfung derart begrenzter Ressourcen zu vermeiden. Und wenn der GC nicht genügend aufgerufen wird (oder die Finalisierung nicht zum richtigen Zeitpunkt ausgeführt wird), erreichen Sie dieses (möglicherweise niedrige) Limit.
Finalisierung ist in der JVM eine "Best-Effort-Sache". Es kann sein, dass es nicht oder nur sehr spät aufgerufen wird ... Insbesondere, wenn Sie viel RAM haben oder wenn Ihr Programm nicht viele Objekte zuweist (oder wenn die meisten von ihnen sterben, bevor sie an einige weitergeleitet werden, die alt genug sind) Generierung durch ein Kopier-Generierungs-GC), kann der GC sehr selten aufgerufen werden, und die Finalisierungen werden möglicherweise nicht sehr oft (oder gar nicht) ausgeführt.
Also
close
Dateideskriptoren möglichst explizit. Wenn Sie Angst haben, diese zu verlieren, verwenden Sie die Finalisierung als zusätzliche Maßnahme, nicht als Hauptmaßnahme.quelle
Betrachten Sie die Sache so: Sie sollten nur Code schreiben, der (a) korrekt ist (ansonsten ist Ihr Programm einfach falsch) und (b) notwendig (ansonsten ist Ihr Code zu groß, was bedeutet, dass mehr RAM erforderlich ist, mehr Zyklen aufgewendet werden nutzlose Dinge, mehr Mühe, es zu verstehen, mehr Zeit für die Pflege usw. usw.)
Überlegen Sie nun, was Sie in einem Finalizer tun möchten. Entweder ist es notwendig. In diesem Fall können Sie es nicht in einen Finalizer einfügen, da Sie nicht wissen, ob es aufgerufen wird. Das ist nicht gut genug. Oder es ist nicht nötig - dann solltest du es gar nicht erst schreiben! In jedem Fall ist es die falsche Wahl, es in den Finalizer zu setzen.
(Beachten Sie, dass die Beispiele , die Sie nennen, wie Datei - Streams zu schließen, sehen , als ob sie sind nicht wirklich notwendig, aber sie sind. Es ist nur so , bis Sie die Grenze für offene Datei - Handles auf Ihrem System treffen, werden Sie nicht Mitteilung , dass Ihr Code ist falsch. Aber diese Grenze ist eine Funktion des Betriebssystems und ist daher noch unberechenbarer als die Politik der JVM für Finalizers, so ist es wirklich, wirklich ist wichtig , dass Sie nicht verschwenden Datei - Handles.)
quelle
finalize()
zum Schließen verwenden, falls ein GC-Zyklus wirklich frei werden muss RAM. Ansonsten würde ich das Objekt im RAM behalten, anstatt es jedes Mal neu generieren und schließen zu müssen, wenn ich es verwenden muss. Sicher, die Ressourcen, die geöffnet werden, werden erst freigegeben, wenn das Objekt in einem bestimmten Zeitraum gecodet wurde. Es kann jedoch sein, dass ich nicht garantieren muss, dass meine Ressourcen zu einem bestimmten Zeitpunkt freigegeben werden.Einer der Hauptgründe, sich nicht auf Finalizer zu verlassen, ist, dass die meisten Ressourcen, die man im Finalizer aufräumen könnte, sehr begrenzt sind. Der Garbage Collector wird nur von Zeit zu Zeit ausgeführt, da das Durchsuchen von Referenzen, um festzustellen, ob etwas freigegeben werden kann, teuer ist. Dies bedeutet, dass es eine Weile dauern kann, bis Ihre Objekte tatsächlich zerstört werden. Wenn Sie beispielsweise viele Objekte haben, die kurzlebige Datenbankverbindungen öffnen, kann der Verbindungspool erschöpft sein, wenn die Finalizer diese Verbindungen bereinigen, während der Garbage Collector darauf wartet, dass er endlich ausgeführt wird und die fertigen Verbindungen freigibt. Aufgrund des Wartens kommt es dann zu einem großen Rückstand an Anfragen in der Warteschlange, die den Verbindungspool schnell wieder erschöpfen. Es'
Durch die Verwendung von "try-with-resources" ist es außerdem einfach, "schließbare" Objekte nach Abschluss zu schließen. Wenn Sie mit diesem Konstrukt nicht vertraut sind, empfehlen wir Ihnen, es sich anzusehen : https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
quelle
Abgesehen davon, warum es im Allgemeinen eine schlechte Idee ist, es dem Finalizer zu überlassen, Ressourcen freizugeben, sind finalisierbare Objekte mit Performance-Overhead verbunden.
Aus Java Theorie und Praxis: Garbage Collection und Performance (Brian Goetz), Finalizer sind nicht dein Freund :
quelle
finalize()
.Mein (am wenigsten) bevorzugter Grund für das Vermeiden
Object.finalize
ist nicht, dass Objekte möglicherweise abgeschlossen werden, nachdem Sie es erwartet haben, sondern dass sie abgeschlossen werden können, bevor Sie es erwartet haben. Das Problem besteht nicht darin, dass ein Objekt, das sich noch im Gültigkeitsbereich befindet, vor dem Verlassen des Gültigkeitsbereichs finalisiert werden kann, wenn Java feststellt, dass es nicht mehr erreichbar ist.Siehe diese Frage für weitere Details. Noch lustiger ist, dass diese Entscheidung möglicherweise erst nach dem Start der Hot-Spot-Optimierung getroffen wird, was das Debuggen schmerzhaft macht.
quelle
HasFinalize.stream
es sich um ein separat finalisierbares Objekt handeln sollte. Das heißt, die Finalisierung vonHasFinalize
sollte nicht finalisiert werden oder zu einer Bereinigung führenstream
. Oder wenn es sollte, dann sollte esstream
unzugänglich machen .In Eclipse erhalte ich eine Warnung, wenn ich vergesse, etwas zu schließen, das
Closeable
/ implementiertAutoCloseable
. Ich bin mir nicht sicher, ob es sich um eine Eclipse-Sache handelt oder ob sie Teil des offiziellen Compilers ist, aber Sie könnten versuchen, ähnliche statische Analyse-Tools zu verwenden, um Ihnen dabei zu helfen. Mit FindBugs können Sie möglicherweise überprüfen, ob Sie vergessen haben, eine Ressource zu schließen.quelle
AutoCloseable
. Es macht es denkbar einfach, Ressourcen mit Try-with-Resources zu verwalten. Dies macht einige der Argumente in der Frage ungültig.Zu Ihrer ersten Frage:
Nun, die JVM bestimmt, wann es ein guter Punkt ist, den für ein Objekt zugewiesenen Speicher zurückzugewinnen. Dies ist nicht unbedingt der Zeitpunkt, an dem die Ressourcenbereinigung, die Sie durchführen möchten
finalize()
, stattfinden soll. Dies wird in der Frage „finalize () auf ein stark erreichbares Objekt in Java 8“ zu SO veranschaulicht . Dort wurde eineclose()
Methode von einerfinalize()
Methode aufgerufen , während der Versuch, von demselben Objekt aus dem Stream zu lesen, noch aussteht. Neben der bekannten Möglichkeit, dass einfinalize()
Anruf zu spät eingeht, besteht die Möglichkeit, dass er zu früh eingeht.Die Prämisse Ihrer zweiten Frage:
ist einfach falsch. Es ist nicht erforderlich, dass eine JVM die Finalisierung unterstützt. Nun, es ist nicht völlig falsch, da Sie es immer noch als "die Anwendung wurde vor dem Abschluss beendet" interpretieren könnten, vorausgesetzt, dass Ihre Anwendung jemals beendet wird.
Beachten Sie jedoch den kleinen Unterschied zwischen "GC" Ihrer ursprünglichen Aussage und dem Begriff "Finalisierung". Die Garbage Collection unterscheidet sich von der Finalisierung. Sobald die Speicherverwaltung feststellt, dass ein Objekt nicht erreichbar ist, kann es einfach seinen Speicherplatz zurückfordern, wenn entweder keine spezielle
finalize()
Methode zur Verfügung steht oder die Finalisierung einfach nicht unterstützt wird oder das Objekt für die Finalisierung in eine Warteschlange gestellt wird. Somit impliziert der Abschluss eines Speicherbereinigungszyklus nicht, dass Finalisierer ausgeführt werden. Dies kann zu einem späteren Zeitpunkt geschehen, wenn die Warteschlange verarbeitet wird, oder überhaupt nicht.Dieser Punkt ist auch der Grund, warum es selbst bei JVMs mit Finalisierungsunterstützung gefährlich ist, sich bei der Bereinigung von Ressourcen darauf zu verlassen. Die Speicherbereinigung ist Teil der Speicherverwaltung und wird daher durch den Speicherbedarf ausgelöst. Es ist möglich, dass die Garbage Collection niemals ausgeführt wird, weil während der gesamten Laufzeit genügend Speicherplatz vorhanden ist (was auch immer noch in die Beschreibung "Die Anwendung wurde beendet, bevor der GC die Möglichkeit zum Ausführen hatte" passt). Es ist auch möglich, dass der GC ausgeführt wird. Danach wird jedoch genügend Speicher freigegeben, sodass die Finalizer-Warteschlange nicht verarbeitet wird.
Mit anderen Worten, auf diese Weise verwaltete native Ressourcen sind der Speicherverwaltung immer noch fremd. Es ist zwar garantiert, dass ein
OutOfMemoryError
Fehler erst ausgelöst wird, nachdem ausreichend versucht wurde, Speicher freizugeben, dies gilt jedoch nicht für native Ressourcen und die Finalisierung. Es ist möglich, dass das Öffnen einer Datei aufgrund unzureichender Ressourcen fehlschlägt, während die Finalisierungswarteschlange voll ist mit Objekten, die diese Ressourcen freigeben könnten, falls der Finalizer jemals ausgeführt wird.quelle