24

Effektives Java sagt:Warum haben Finalizer eine "strenge Leistungseinbuße"?

Es gibt eine starke Leistungseinbuße für die Verwendung von Finalizern.

Warum ist es langsamer, ein Objekt mit den Finalizern zu zerstören?

+1

Sie können diesen Artikel mögen, es spricht darüber, wie Finalizer Objekte wieder erreichbar machen können, usw. Es zeigt auch, warum die Zusammensetzung in einigen Fällen den Tag retten kann (statt Implementierungsvererbung): http: //java.sun. com/developer/technicalArticles/javase/finalisation/ – SyntaxT3rr0r

Antwort

22

Wegen der Art, wie der Garbage Collector funktioniert. Für die Leistung verwenden die meisten Java-GCs einen Kopier-Collector, bei dem kurzlebige Objekte einem "eden" -Block des Speichers zugeordnet werden. Wenn es für diese Generation von Objekten erforderlich ist, gesammelt zu werden, muss der GC nur die Objekte kopieren sind zu einem dauerhafteren Speicherplatz noch "lebendig" und können dann den gesamten "eden" Speicherblock auf einmal löschen (freigeben). Dies ist effizient, da der meiste Java-Code viele tausend Instanzen von Objekten (umrahmte Primitive, temporäre Arrays usw.) mit Lebensdauern von nur wenigen Sekunden erzeugt.

Wenn Sie jedoch Finalizer im Mix haben, kann der GC nicht einfach eine ganze Generation auf einmal löschen. Stattdessen müssen alle Objekte in dieser Generation ermittelt werden, die finalisiert werden müssen, und sie werden in einem Thread in eine Warteschlange eingereiht, der die Finalizer tatsächlich ausführt. In der Zwischenzeit kann der GC die Objekte nicht effizient säubern. Entweder muss er sie länger am Leben erhalten, als sie eigentlich sein sollten, oder sie muss das Sammeln anderer Objekte oder beides verzögern. Außerdem haben Sie die willkürliche Wartezeit, um die Finalizer tatsächlich auszuführen.

All diese Faktoren summieren sich zu einer beträchtlichen Laufzeiteinbuße, weshalb eine deterministische Finalisierung (unter Verwendung einer close() Methode oder ähnlich, um den Objektzustand explizit zu finalisieren) normalerweise bevorzugt wird.

+1

Dies konzentriert sich natürlich auf Probleme mit einem Generationskollektor. Andere GC-Strategien haben unterschiedliche Probleme. Aber alles läuft darauf hinaus, dass der GC zusätzliche Arbeit leisten muss, einschließlich mindestens zwei Durchgänge über ein Objekt, um es zu befreien; Eins, um es der Finalize-Warteschlange hinzuzufügen, und eins, um es nach der Finalisierung freizugeben. –

+0

Wäre es richtig zu denken, dass mehrere häufig verwendete Java-API-Klassen Finalizer haben, um O/S-Ressourcen freizugeben? Ich denke an 'FileOutputStream'. Es ist daher unwahrscheinlich, dass Finalizer für einige Objekte GC von Objekten verzögern, die keine Finalizer verwenden, da die meisten Programme betroffen wären. – Raedwald

+1

@ Raedwald: Richtig. Zum Beispiel hat die OpenJDK-Implementierung von 'FileOutputStream' einen Finalizer, den Sie sehen können, indem Sie sich die OpenJDK-Quelle ansehen. (Ich bin jedoch nicht in der Lage, etwas zu finden, was * die Implementierung der Standardbibliothek erfordert, um Finalizer zu verwenden.) In der Praxis werden also Objekte, die ansonsten für GC in Frage kommen, aber noch nicht abgeschlossen sind, auf die nächste ältere Generation hochgestuft (Survivor Space oder Tenured), während der Finalizer in die Warteschlange gestellt wird. Der tatsächliche Speicher wird jedoch erst wieder freigegeben, wenn die nächste ältere Generation das nächste Mal erfasst wird. –

1

Mein Gedanke ist dies: Java ist eine Garbage Collection Sprache, die Speicher auf der Grundlage seiner eigenen internen Algorithmen freigibt. Von Zeit zu Zeit durchsucht der GC den Heap, bestimmt, auf welche Objekte nicht mehr verwiesen wird, und hebt den Speicher auf. Ein Finalizer unterbricht dies und erzwingt die Freigabe von Speicher außerhalb des GC-Zyklus, wodurch möglicherweise Ineffizienzen verursacht werden. Ich denke, Best Practices sind die Verwendung von Finalizern nur dann, wenn sie absolut notwendig sind, wie das Freigeben von Dateigriffen oder das Schließen von DB-Verbindungen, die deterministisch gemacht werden sollten.

+1

Macht es wirklich, oder nur vorschlagen? – corsiKa

+0

Meistens richtig, aber Finalizer verursachen keine Speicherfreigabe außerhalb eines GC-Zyklus. Wenn der GC feststellt, dass ein Objekt abgeschlossen werden muss, "reaktiviert" es stattdessen und verhindert, dass das Objekt gesammelt wird, bis der Finalizer ausgeführt wurde. Es kann jedoch eine Weile dauern, da (IIRC) Finalisierer nicht ausgeführt werden bis zum nächsten Mal, wenn die Tenure-Generation gesammelt wird. –

+1

"Ich denke, Best Practices sind die Verwendung von Finalizern nur dann, wenn ABSOLUT notwendig ist, wie das Freigeben von Dateigriffen oder das Schließen von DB-Verbindungen." Beachten Sie, dass Finalizer genau dann nicht * geeignet * sind, weil der Finalizer möglicherweise zu spät oder gar nicht ausgeführt wird. – sleske

0

Ein Grund, an den ich denken kann, ist, dass eine explizite Speicherbereinigung unnötig ist, wenn Ihre Ressourcen alle Java-Objekte sind und kein systemeigener Code.

1

Ich habe gerade meine Kopie Effektives Java von meinem Schreibtisch abgeholt, um zu sehen, worauf er sich bezieht.

Wenn Sie Kapitel 2, Abschnitt 6 gelesen haben, geht er detailliert auf die verschiedenen Leistungstreffer ein.

You can't know when the finalizer will run, or even if it will at all. Because those resources may never be claimed, you will have to run with fewer resources.

Ich würde empfehlen, die Gesamtheit des Abschnitts lesen - es erklärt die Dinge viel besser, als ich hier Papagei kann.

1

Wenn Sie die Dokumentation von finalize() genau lesen, werden Sie feststellen, dass Finalizer ein Objekt aktivieren, um zu verhindern, dass es vom GC erfasst wird.

Wenn kein Finalizer vorhanden ist, kann das Objekt einfach entfernt werden und muss nicht mehr beachtet werden. Wenn es jedoch einen Finalizer gibt, muss dieser nachher überprüft werden, wenn das Objekt nicht wieder "sichtbar" wird.

Ohne genau zu wissen, wie die aktuelle Java-Speicherbereinigung implementiert ist (da es verschiedene Java-Implementierungen gibt, gibt es auch verschiedene GCs), können Sie davon ausgehen, dass der GC zusätzliche Arbeit leisten muss, wenn ein Objekt vorhanden ist ein Finalizer, wegen dieser Eigenschaft.

9

tatsächlich laufen in ein solches Problem zu haben:

In der Sun HotSpot JVM, Finalizers auf einem Faden verarbeitet, die eine feste, niedrige Priorität gegeben wird. In einer High-Load-Anwendung können Finalization-Objekte schneller erstellt werden, als der Finalization-Thread mit niedriger Priorität sie verarbeiten kann. Der Speicherplatz auf dem Heap, der von den Objekten verwendet wird, für die die Finalisierung noch aussteht, ist für andere Zwecke nicht verfügbar. Schließlich kann Ihre Anwendung die gesamte Zeit mit dem Sammeln von Daten verbringen, da der gesamte verfügbare Speicher von Objekten verwendet wird, deren Fertigstellung noch aussteht.

Dies ist natürlich zusätzlich zu den anderen vielen Gründen, Finalizer nicht zu verwenden, die in Effective Java beschrieben werden.