Frage

Nach dem Lesen diese Frage, wurde ich an die Zeit erinnert, als mir Java beigebracht wurde und mir gesagt wurde, ich solle niemals finalize() aufrufen oder den Garbage Collector ausführen, weil „es eine große Blackbox ist, um die man sich nie Sorgen machen muss“.Kann jemand die Begründung dafür auf ein paar Sätze zusammenfassen?Ich bin mir sicher, dass ich zu diesem Thema einen technischen Bericht von Sun lesen könnte, aber ich denke, eine nette, kurze und einfache Antwort würde meine Neugier befriedigen.

War es hilfreich?

Lösung

Die kurze Antwort:Java Garbage Collection ist ein sehr fein abgestimmtes Tool.System.gc() ist ein Vorschlaghammer.

Der Heap von Java ist in verschiedene Generationen unterteilt, die jeweils mit einer anderen Strategie gesammelt werden.Wenn Sie einer fehlerfreien App einen Profiler hinzufügen, werden Sie feststellen, dass diese sehr selten die teuersten Arten von Sammlungen ausführen muss, da die meisten Objekte vom schneller kopierenden Collector der jungen Generation abgefangen werden.

Der direkte Aufruf von System.gc() löst zwar technisch gesehen nicht garantiert irgendetwas aus, führt in der Praxis jedoch zu einer teuren, umwerfenden vollständigen Heap-Sammlung.Das ist fast immer das Falsche.Sie denken, Sie sparen Ressourcen, aber in Wirklichkeit verschwenden Sie sie ohne guten Grund, indem Sie Java dazu zwingen, alle Ihre Live-Objekte „nur für den Fall“ noch einmal zu überprüfen.

Wenn Sie Probleme mit GC-Pausen in kritischen Momenten haben, ist es besser, die JVM so zu konfigurieren, dass sie den gleichzeitigen Mark/Sweep-Kollektor verwendet, der speziell zur Minimierung der Pausenzeit entwickelt wurde, als zu versuchen, das Problem einfach mit dem Vorschlaghammer anzugehen es weiter brechen.

Das Sun-Dokument, an das Sie gedacht haben, ist hier: Optimierung der Java SE 6 HotSpot™ Virtual Machine Garbage Collection

(Noch etwas, was Sie vielleicht nicht wissen:Durch die Implementierung einer finalize()-Methode für Ihr Objekt wird die Speicherbereinigung langsamer.Erstens wird es dauern zwei GC wird ausgeführt, um das Objekt einzusammeln:Eine zum Ausführen von finalize() und die nächste, um sicherzustellen, dass das Objekt während der Finalisierung nicht wiederbelebt wurde.Zweitens müssen Objekte mit finalize()-Methoden vom GC als Sonderfälle behandelt werden, da sie einzeln gesammelt werden müssen und nicht einfach in großen Mengen weggeworfen werden können.)

Andere Tipps

Kümmern Sie sich nicht um Finalizer.

Wechseln Sie zur inkrementellen Speicherbereinigung.

Wenn Sie dem Garbage Collector helfen möchten, löschen Sie Verweise auf Objekte, die Sie nicht mehr benötigen, auf Null.Weniger Pfad, dem man folgen muss = expliziterer Müll.

Vergessen Sie nicht, dass (nicht statische) Instanzen der inneren Klasse Verweise auf die Instanz ihrer übergeordneten Klasse behalten.Ein Inner-Class-Thread bewahrt also viel mehr Ballast auf, als Sie vielleicht erwarten.

Ganz ähnlich gilt: Wenn Sie Serialisierung verwenden und temporäre Objekte serialisiert haben, müssen Sie die Serialisierungs-Caches löschen, indem Sie ObjectOutputStream.reset() aufrufen. Andernfalls verliert Ihr Prozess Speicher und stürzt schließlich ab.Der Nachteil besteht darin, dass nichtflüchtige Objekte erneut serialisiert werden.Das Serialisieren temporärer Ergebnisobjekte kann etwas chaotischer sein, als Sie vielleicht denken!

Erwägen Sie die Verwendung weicher Referenzen.Wenn Sie nicht wissen, was Soft-Referenzen sind, lesen Sie das Javadoc für java.lang.ref.SoftReference

Vermeiden Sie Phantom-Referenzen und schwache Referenzen, es sei denn, Sie werden wirklich aufgeregt.

Wenn Sie den GC wirklich nicht tolerieren können, verwenden Sie Realtime Java.

Nein, ich mache keine Witze.

Die Referenzimplementierung kann kostenlos heruntergeladen werden und das Buch von Peter Dibbles von SUN ist wirklich eine gute Lektüre.

Was die Finalisierer betrifft:

  1. Sie sind praktisch nutzlos.Es ist nicht garantiert, dass sie rechtzeitig oder überhaupt aufgerufen werden (wenn der GC nie ausgeführt wird, werden dies auch keine Finalisierer tun).Das bedeutet, dass Sie sich im Allgemeinen nicht auf sie verlassen sollten.
  2. Es kann nicht garantiert werden, dass Finalizer idempotent sind.Der Garbage Collector legt großen Wert darauf, sicherzustellen, dass er nie aufgerufen wird finalize() mehr als einmal am selben Objekt.Bei gut geschriebenen Objekten spielt es keine Rolle, aber bei schlecht geschriebenen Objekten kann der mehrmalige Aufruf von finalize zu Problemen führen (z. B.Doppelte Veröffentlichung einer nativen Ressource ...Absturz).
  3. Jeden Objekt, das ein hat finalize() Methode sollte auch eine bieten close() (oder eine ähnliche) Methode.Dies ist die Funktion, die Sie aufrufen sollten.z.B., FileInputStream.close().Es gibt keinen Grund anzurufen finalize() wenn Sie eine geeignetere Methode haben Ist soll von Ihnen aufgerufen werden.

Angenommen, Finalizer ähneln ihrem .NET-Namensvetter, dann müssen Sie diese nur dann wirklich aufrufen, wenn Sie über Ressourcen wie Dateihandles verfügen, die auslaufen können.In den meisten Fällen verfügen Ihre Objekte nicht über diese Referenzen, sodass sie nicht aufgerufen werden müssen.

Es ist schlecht zu versuchen, den Müll einzusammeln, weil es nicht wirklich Ihr Müll ist.Sie haben die VM angewiesen, beim Erstellen von Objekten etwas Speicher zu reservieren, und der Garbage Collector verbirgt Informationen über diese Objekte.Intern führt der GC Optimierungen an den von ihm vorgenommenen Speicherzuweisungen durch.Wenn Sie manuell versuchen, den Müll einzusammeln, wissen Sie nicht, was der GC festhalten und loswerden möchte, Sie zwingen ihn nur dazu.Dadurch bringen Sie interne Berechnungen durcheinander.

Wenn Sie mehr darüber wüssten, was der GC intern vorhat, könnten Sie vielleicht fundiertere Entscheidungen treffen, aber dann haben Sie die Vorteile des GC verpasst.

Das eigentliche Problem beim Schließen von Betriebssystemhandles in finalize besteht darin, dass finalize in keiner garantierten Reihenfolge ausgeführt wird.Aber wenn Sie Handles für die Dinge haben, die blockieren (denken Sie z. B.Sockets) möglicherweise kann Ihr Code in eine Deadlock-Situation geraten (überhaupt nicht trivial).

Daher bin ich dafür, Handles explizit in vorhersehbarer und geordneter Weise zu schließen.Grundsätzlich sollte Code für den Umgang mit Ressourcen folgendem Muster folgen:

SomeStream s = null;
...
try{
   s = openStream();
   ....
   s.io();
   ...
} finally {
   if (s != null) {
       s.close();
       s = null;
   }
}

Noch komplizierter wird es, wenn man eigene Klassen schreibt, die über JNI und offene Handles funktionieren.Sie müssen sicherstellen, dass die Griffe geschlossen (freigegeben) sind und dass dies nur einmal geschieht.Ein häufig übersehenes Betriebssystem-Handle in Desktop J2SE ist Graphics[2D].Sogar BufferedImage.getGrpahics() kann Ihnen möglicherweise das Handle zurückgeben, das auf einen Grafiktreiber verweist (der tatsächlich die Ressource auf der GPU hält).Wenn Sie es nicht selbst veröffentlichen und es dem Garbage Collector überlassen, die Arbeit zu erledigen, kann es zu seltsamen OutOfMemory- und ähnlichen Situationen kommen, in denen Ihnen die mit der Grafikkarte zugeordneten Bitmaps ausgehen, Sie aber immer noch über genügend Speicher verfügen.Meiner Erfahrung nach kommt es ziemlich häufig vor, wenn in engen Schleifen mit Grafikobjekten gearbeitet wird (Miniaturansichten extrahieren, skalieren, schärfen usw.).

Grundsätzlich übernimmt GC nicht die Verantwortung des Programmierers für die korrekte Ressourcenverwaltung.Es kümmert sich nur um das Gedächtnis und sonst nichts.Der Stream.finalize-Aufruf close() wäre meiner Meinung nach besser implementiert und würde die Ausnahme new RuntimeError("Garbage Collection des Streams, der noch geöffnet ist") auslösen.Es erspart Stunden und Tage des Debuggens und Bereinigens von Code, nachdem die schlampigen Amateure den Überblick verloren haben.

Viel Spaß beim Codieren.

Frieden.

Der GC führt zahlreiche Optimierungen durch, um den richtigen Abschluss der Dinge zu gewährleisten.

Wenn Sie also nicht damit vertraut sind, wie der GC tatsächlich funktioniert und wie er Generationen markiert, wird ein manueller Aufruf von „finalize“ oder „start GC'ing“ wahrscheinlich eher der Leistung schaden als helfen.

Vermeiden Sie Finalizer.Es gibt keine Garantie dafür, dass sie rechtzeitig aufgerufen werden.Es kann ziemlich lange dauern, bis das Speicherverwaltungssystem (d. h. der Garbage Collector) entscheidet, ein Objekt mit einem Finalizer zu sammeln.

Viele Leute verwenden Finalizer, um beispielsweise Socket-Verbindungen zu schließen oder temporäre Dateien zu löschen.Auf diese Weise machen Sie Ihr Anwendungsverhalten unvorhersehbar und hängen davon ab, wann die JVM Ihr Objekt per GC prüft.Dies kann zu „Nicht genügend Arbeitsspeicher“-Szenarien führen, nicht weil der Java-Heap erschöpft ist, sondern weil dem System die Handles für eine bestimmte Ressource ausgehen.

Beachten Sie außerdem, dass die Einführung von Aufrufen von System.gc() oder solchen Hammers in Ihrer Umgebung möglicherweise gute Ergebnisse liefert, sich aber nicht unbedingt auf andere Systeme übertragen lässt.Nicht jeder führt die gleiche JVM aus, es gibt viele, SUN, IBM J9, BEA JRockit, Harmony, OpenJDK usw.Diese JVM entsprechen alle dem JCK (also denjenigen, die offiziell getestet wurden), bieten aber viel Freiheit, wenn es darum geht, die Dinge schnell zu machen.GC ist einer der Bereiche, in die jeder stark investiert.Der Einsatz eines Hammers macht diese Anstrengung oft zunichte.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top