Warum ist es schlecht, System.gc () aufzurufen?


326

Nachdem ich eine Frage zum Erzwingen von Objekten in Java (der Typ löschte eine 1,5-GB-HashMap) mit beantwortet hatteSystem.gc() , wurde mir gesagt, dass es eine schlechte Praxis sei, System.gc()manuell aufzurufen , aber die Kommentare waren nicht ganz überzeugend. Außerdem schien es niemand zu wagen, meine Antwort zu stimmen oder zu stimmen.

Dort wurde mir gesagt, dass es eine schlechte Praxis ist, aber dann wurde mir auch gesagt, dass Garbage Collector-Läufe die Welt nicht mehr systematisch stoppen und dass sie auch von der JVM effektiv nur als Hinweis verwendet werden könnten, also bin ich irgendwie Bei Verlust.

Ich verstehe, dass die JVM normalerweise besser als Sie weiß, wann sie Speicher zurückfordern muss. Ich verstehe auch, dass es dumm ist, sich um ein paar Kilobyte Daten zu sorgen. Ich verstehe auch, dass selbst Megabyte an Daten nicht mehr so ​​sind wie vor ein paar Jahren. Aber immer noch 1,5 Gigabyte? Und Sie wissen, dass etwa 1,5 GB Daten im Speicher herumhängen. Es ist nicht so, als wäre es ein Schuss in die Dunkelheit. Ist System.gc()systematisch schlecht oder gibt es einen Punkt, an dem es in Ordnung wird?

Die Frage ist also eigentlich doppelt:

  • Warum ist oder ist es nicht schlecht anzurufen System.gc()? Ist es unter bestimmten Implementierungen wirklich nur ein Hinweis auf die JVM oder handelt es sich immer um einen vollständigen Erfassungszyklus? Gibt es wirklich Garbage Collector-Implementierungen, die ihre Arbeit erledigen können, ohne die Welt zu stoppen? Bitte werfen Sie ein Licht auf die verschiedenen Behauptungen, die in den Kommentaren zu meiner Antwort gemacht wurden .
  • Wo ist die Schwelle? Ist es nie eine gute Idee anzurufen System.gc()oder gibt es Zeiten, in denen es akzeptabel ist? Wenn ja, wie sind diese Zeiten?

4
Ich denke, ein guter Zeitpunkt, um System.gc () aufzurufen, ist, wenn Sie etwas tun, das bereits ein langer Ladevorgang ist. Zum Beispiel arbeite ich an einem Spiel und plane, System.gc () aufzurufen, wenn das Spiel am Ende des Ladevorgangs ein neues Level lädt. Der Benutzer wartet bereits ein wenig, und die zusätzliche Leistungssteigerung kann sich lohnen. Ich werde aber auch eine Option in den Konfigurationsbildschirm einfügen, um dieses Verhalten zu deaktivieren.
Ricket

41
Bozho: Beachten Sie das erste Wort der Frage. WARUM ist es die beste Praxis, es nicht zu nennen? Nur ein Mantra zu wiederholen, erklärt nichts Verdammtes.
Nur meine richtige Meinung

1
Ein anderer Fall wurde in java-monitor.com/forum/showthread.php?t=188 erklärt. Dort wird erklärt, wie es eine schlechte Praxis sein kann, System.gc ()
Shirishkumar Bari

Antworten:


243

Der Grund, den jeder immer zu vermeiden sagt, System.gc()ist, dass es ein ziemlich guter Indikator für grundlegend kaputten Code ist . Jeder Code, dessen Richtigkeit davon abhängt, ist mit Sicherheit fehlerhaft. Alle, die sich für die Leistung darauf verlassen, sind höchstwahrscheinlich defekt.

Sie wissen nicht, unter welcher Art von Müllsammler Sie laufen. Es gibt sicherlich einige, die die Welt nicht "aufhalten", wie Sie behaupten, aber einige JVMs sind nicht so schlau oder tun es aus verschiedenen Gründen (vielleicht telefonieren sie?) Nicht. Sie wissen nicht, was es tun wird.

Es ist auch nicht garantiert, dass etwas getan wird. Die JVM ignoriert Ihre Anfrage möglicherweise vollständig.

Die Kombination aus "Sie wissen nicht, was es tun wird", "Sie wissen nicht, ob es überhaupt helfen wird" und "Sie sollten es sowieso nicht nennen müssen" sind der Grund, warum die Leute dies im Allgemeinen so eindringlich sagen du solltest es nicht nennen. Ich denke, es ist ein Fall von "Wenn Sie fragen müssen, ob Sie dies verwenden sollten, sollten Sie nicht"


BEARBEITEN , um einige Bedenken aus dem anderen Thread auszuräumen:

Nachdem Sie den von Ihnen verlinkten Thread gelesen haben, möchte ich noch auf einige weitere Dinge hinweisen. Zunächst schlug jemand vor, dass der Aufruf gc()möglicherweise Speicher an das System zurückgibt. Das ist sicherlich nicht unbedingt wahr - der Java-Heap selbst wächst unabhängig von Java-Zuweisungen.

Wie in speichert die JVM Speicher (viele zehn Megabyte) und vergrößert den Heap nach Bedarf. Dieser Speicher wird nicht unbedingt an das System zurückgegeben, selbst wenn Sie Java-Objekte freigeben. Es ist völlig kostenlos, den zugewiesenen Speicher für zukünftige Java-Zuweisungen beizubehalten.

Um zu zeigen, dass es möglich ist, dass System.gc()nichts funktioniert, sehen Sie:

http://bugs.sun.com/view_bug.do?bug_id=6668279

und insbesondere, dass es eine VM-Option -XX: DisableExplicitGC gibt .


1
Möglicherweise können Sie ein seltsames Rube Goldberg-ähnliches Setup erstellen, bei dem die Methode, mit der der GC ausgeführt wird, die Richtigkeit Ihres Codes beeinflusst. Vielleicht maskiert es eine seltsame Threading-Interaktion, oder vielleicht hat ein Finalizer einen signifikanten Einfluss auf die Ausführung des Programms. Ich bin mir nicht ganz sicher, ob es möglich ist, aber es könnte sein, also dachte ich mir, ich würde es erwähnen.
Steven Schlansker

2
@zneak Sie könnten zum Beispiel kritischen Code in Finalizer eingefügt haben (was grundlegend gebrochener Code ist)
Martin

3
Ich möchte hinzufügen, dass es einige Eckfälle gibt, in denen dies System.gc()nützlich und möglicherweise sogar notwendig ist. Beispielsweise kann es in UI-Anwendungen unter Windows den Wiederherstellungsprozess eines Fensters erheblich beschleunigen, wenn Sie System.gc () aufrufen, bevor Sie das Fenster minimieren (insbesondere, wenn es für einige Zeit minimiert bleibt und Teile des Prozesses ausgetauscht werden Platte).
Joachim Sauer

2
@AndrewJanke Ich würde sagen, dass Code, der WeakReferences für Objekte verwendet, an denen Sie festhalten möchten, von Anfang an falsch ist, ob Speicherbereinigung oder nicht. Sie hätten das gleiche Problem in C ++ mit std::weak_ptr(obwohl Sie das Problem in einer C ++ - Version möglicherweise früher als in einer Java-Version bemerken würden, da die Objektzerstörung nicht wie normalerweise bei der Finalisierung zurückgestellt würde).
JAB

2
@rebeccah Das ist ein Fehler, also ja, ich würde es "sicherlich kaputt" nennen. Die Tatsache, dass dies System.gc()behoben wird, ist eine Problemumgehung und keine gute Codierungspraxis.
Steven Schlansker

149

Es wurde bereits erklärt, dass das Aufrufen system.gc() möglicherweise nichts bewirkt und dass jeder Code, der den Garbage Collector zum Ausführen "benötigt", fehlerhaft ist.

Der pragmatische Grund, warum es eine schlechte Praxis ist, anzurufen, System.gc()ist jedoch, dass es ineffizient ist. Und im schlimmsten Fall ist es schrecklich ineffizient ! Lassen Sie mich erklären.

Ein typischer GC-Algorithmus identifiziert Müll, indem er alle Nicht-Müll-Objekte im Heap durchläuft und daraus schließt, dass jedes nicht besuchte Objekt Müll sein muss. Daraus können wir die Gesamtarbeit einer Garbage Collection modellieren, die aus einem Teil besteht, der proportional zur Menge der Live-Daten ist, und einem anderen Teil, der proportional zur Menge des Garbage ist. dh work = (live * W1 + garbage * W2).

Angenommen, Sie führen in einer Anwendung mit einem Thread Folgendes aus.

System.gc(); System.gc();

Der erste Anruf wird (wir sagen voraus) (live * W1 + garbage * W2)funktionieren und den ausstehenden Müll loswerden.

Der zweite Anruf (live* W1 + 0 * W2)funktioniert und fordert nichts zurück. Mit anderen Worten, wir haben (live * W1)Arbeit geleistet und absolut nichts erreicht .

Wir können die Effizienz des Sammlers als den Arbeitsaufwand modellieren, der zum Sammeln einer Mülleinheit erforderlich ist. dh efficiency = (live * W1 + garbage * W2) / garbage. Um den GC so effizient wie möglich zu gestalten, müssen wir den Wert maximieren,garbage wenn wir den GC ausführen. dh warten, bis der Haufen voll ist. (Und machen Sie den Haufen so groß wie möglich. Aber das ist ein separates Thema.)

Wenn die Anwendung nicht stört (durch Aufrufen System.gc()), wartet der GC, bis der Heap voll ist, bevor er ausgeführt wird, was zu einer effizienten Sammlung von Garbage 1 führt . Wenn die Anwendung den GC jedoch zur Ausführung zwingt, ist der Heap wahrscheinlich nicht voll, und das Ergebnis ist, dass der Müll ineffizient gesammelt wird. Und je öfter die Anwendung die GC erzwingt, desto ineffizienter wird die GC.

Hinweis: Die obige Erklärung beschönigt die Tatsache, dass ein typischer moderner GC den Heap in "Leerzeichen" unterteilt, der GC den Heap dynamisch erweitern kann, die Arbeitsgruppe der Nicht-Garbage-Objekte der Anwendung variieren kann und so weiter. Trotzdem gilt auf allen Ebenen das gleiche Grundprinzip für alle echten Müllsammler 2 . Es ist ineffizient, den GC zum Ausführen zu zwingen.


1 - So funktioniert der "Durchsatz" -Kollektor. Gleichzeitige Kollektoren wie CMS und G1 verwenden unterschiedliche Kriterien, um zu entscheiden, wann der Garbage Collector gestartet werden soll.

2 - Ich schließe auch Speichermanager aus, die ausschließlich Referenzzählung verwenden, aber keine aktuelle Java-Implementierung verwendet diesen Ansatz ... aus gutem Grund.


45
+1 Gute Erklärung. Beachten Sie jedoch, dass diese Argumentation nur gilt, wenn Sie sich für den Durchsatz interessieren. Wenn Sie die Latenz an bestimmten Punkten optimieren möchten, ist es möglicherweise sinnvoll, GC zu erzwingen. ZB (hypothetisch gesehen) möchten Sie in einem Spiel möglicherweise Verzögerungen während des Levels vermeiden, aber Sie kümmern sich nicht um Verzögerungen beim Laden des Levels. Dann wäre es sinnvoll, die GC nach dem Laden des Niveaus zu erzwingen. Es verringert zwar den Gesamtdurchsatz, aber das optimieren Sie nicht.
Sleske

2
@sleske - was du sagst ist wahr. Das Ausführen des GC zwischen Ebenen ist jedoch eine Band-Aid-Lösung ... und löst das Latenzproblem nicht, wenn die Ebenen lange genug dauern, dass Sie den GC ohnehin während eines Levels ausführen müssen. Ein besserer Ansatz ist die Verwendung eines gleichzeitigen Garbage Collectors (niedrige Pause) ... wenn die Plattform dies unterstützt.
Stephen C

Nicht ganz sicher, was Sie unter "benötigt den Garbage Collector zum Ausführen" verstehen. Die Bedingungen, die Anwendungen vermeiden müssen, sind: Zuweisungsfehler, die niemals behoben werden können, und hoher GC-Overhead. Das zufällige Aufrufen von System.gc () führt häufig zu einem hohen GC-Overhead.
Kirk

@Kirk - "Das zufällige Aufrufen von System.gc () führt häufig zu einem hohen GC-Overhead." . Ich weiß das. So würde jeder, der meine Antwort gelesen und verstanden hatte.
Stephen C

@Kirk - "Nicht ganz sicher, was Sie meinen ..." - Ich meine Programme, bei denen das "richtige" Verhalten eines Programms davon abhängt, dass der GC zu einem bestimmten Zeitpunkt ausgeführt wird. zB um Finalizer auszuführen oder WeakReferences oder SoftReferences zu brechen. Es ist eine wirklich schlechte Idee, dies zu tun ... aber das ist es, worüber ich spreche. Siehe auch die Antwort von Steven Schlansker.
Stephen C

47

Viele Leute scheinen dir zu sagen, dass du das nicht tun sollst. Ich stimme dir nicht zu. Wenn Sie nach einem großen Ladevorgang wie dem Laden eines Levels glauben, dass:

  1. Sie haben viele Objekte, die nicht erreichbar sind und möglicherweise nicht gefunden wurden. und
  2. Sie denken, der Benutzer könnte an dieser Stelle eine kleine Verlangsamung in Kauf nehmen

Das Aufrufen von System.gc () kann nicht schaden. Ich sehe es wie das inlineSchlüsselwort c / c ++ . Es ist nur ein Hinweis für den GC, dass Sie als Entwickler entschieden haben, dass Zeit / Leistung nicht so wichtig ist wie gewöhnlich und dass ein Teil davon verwendet werden könnte, um Speicher zurückzugewinnen.

Der Rat, sich nicht darauf zu verlassen, dass etwas getan wird, ist richtig. Verlassen Sie sich nicht darauf, dass es funktioniert, aber es ist vollkommen in Ordnung, den Hinweis zu geben, dass jetzt ein akzeptabler Zeitpunkt zum Sammeln ist. Ich würde lieber Zeit an einem Punkt im Code verschwenden, an dem es keine Rolle spielt (Ladebildschirm), als wenn der Benutzer aktiv mit dem Programm interagiert (wie während eines Levels eines Spiels).

Es gibt eine Zeit, in der ich die Erfassung erzwingen werde : Wenn ich versuche herauszufinden, ob ein bestimmtes Objekt leckt (entweder nativer Code oder große, komplexe Rückrufinteraktion. Oh, und jede UI-Komponente, die Matlab auch nur ansieht). Dies sollte niemals verwendet werden im Produktionscode.


3
+1 für GC bei der Analyse auf Mem-Lecks. Beachten Sie, dass die Informationen zur Heap-Nutzung (Runtime.freeMemory () et al.) Nur nach dem Erzwingen eines GC wirklich aussagekräftig sind. Andernfalls hängt dies davon ab, wann sich das System zuletzt die Mühe gemacht hat, einen GC auszuführen.
Sleske

4
Es ist nicht schlimm, System.gc () aufzurufen, es könnte einen stop the worldAnsatz erfordern , und dies ist ein echter Schaden, wenn es passiert
bestsss

7
Es ist schädlich, den Garbage Collector explizit aufzurufen. Das Aufrufen des GC zur falschen Zeit verschwendet CPU-Zyklen. Sie (der Programmierer) haben nicht genügend Informationen, um festzustellen, wann der richtige Zeitpunkt ist ... aber die JVM tut dies.
Stephen C

Jetty ruft nach dem Start zwei Mal System.gc () auf, und ich habe selten gesehen, dass dies einen Unterschied macht.
Kirk

Nun, die Jetty-Entwickler haben einen Fehler. Es würde einen Unterschied machen ... selbst wenn der Unterschied schwer zu quantifizieren ist.
Stephen C

31

Die Leute haben gute Arbeit geleistet und erklärt, warum sie NICHT verwenden sollten. Deshalb werde ich Ihnen einige Situationen erläutern, in denen Sie sie verwenden sollten:

(Die folgenden Kommentare gelten für Hotspot, der unter Linux mit dem CMS-Collector ausgeführt wird. Ich bin zuversichtlich, dass System.gc()dies tatsächlich immer eine vollständige Garbage Collection aufruft.)

  1. Nach der ersten Arbeit beim Starten Ihrer Anwendung kann es zu einer schrecklichen Speichernutzung kommen. Die Hälfte Ihrer festen Generation könnte voller Müll sein, was bedeutet, dass Sie Ihrem ersten CMS so viel näher sind. In Anwendungen, in denen dies wichtig ist, ist es keine schlechte Idee, System.gc () aufzurufen, um Ihren Heap auf den Startzustand von Live-Daten zurückzusetzen.

  2. Wenn Sie Ihre Heap-Nutzung genau überwachen, möchten Sie genau wie # 1 genau wissen, wie hoch Ihre Basisspeicherauslastung ist. Wenn die ersten 2 Minuten der Betriebszeit Ihrer Anwendung vollständig initialisiert sind, werden Ihre Daten durcheinander gebracht, es sei denn, Sie erzwingen (ähm ... "vorschlagen") die volle gc im Voraus.

  3. Möglicherweise haben Sie eine Anwendung, die so konzipiert ist, dass sie während der Ausführung niemals etwas für die fest angestellte Generation heraufbeschwört. Aber vielleicht müssen Sie einige Daten im Voraus initialisieren, die nicht so groß sind, dass sie automatisch zur dauerhaften Generation verschoben werden. Wenn Sie System.gc () nicht aufrufen, nachdem alles eingerichtet wurde, können Ihre Daten in der neuen Generation gespeichert werden, bis die Zeit für die Heraufstufung gekommen ist. Plötzlich wird Ihre Super-Duper-Anwendung mit geringer Latenz und niedrigem GC mit einer RIESIGEN (natürlich relativ) Latenzstrafe belegt, wenn Sie diese Objekte während des normalen Betriebs bewerben.

  4. Manchmal ist es nützlich, einen System.gc-Aufruf in einer Produktionsanwendung verfügbar zu haben, um das Vorhandensein eines Speicherverlusts zu überprüfen. Wenn Sie wissen, dass der Satz von Live-Daten zum Zeitpunkt X in einem bestimmten Verhältnis zum Satz von Live-Daten zum Zeitpunkt Y vorhanden sein sollte, kann es hilfreich sein, System.gc () zum Zeitpunkt X und zum Zeitpunkt Y aufzurufen und die Speichernutzung zu vergleichen .


1
Für die meisten Garbage Collectors der Generation müssen Objekte der neuen Generation eine bestimmte (häufig konfigurierbare) Anzahl von Garbage Collections überleben. Wenn Sie also System.gc()einmal anrufen , um die Förderung von Objekten zu erzwingen, erhalten Sie nichts. Und Sie möchten sicherlich nicht System.gc()acht Mal hintereinander anrufen und beten, dass die Aktion jetzt abgeschlossen ist und die gesparten Kosten einer späteren Aktion die Kosten mehrerer vollständiger GCs rechtfertigen. Abhängig vom GC-Algorithmus trägt das Bewerben vieler Objekte möglicherweise nicht einmal die tatsächlichen Kosten, da nur der Speicher der alten Generation zugewiesen oder gleichzeitig kopiert wird…
Holger,

11

Dies ist eine sehr lästige Frage, und ich glaube, dass viele dazu beitragen, dass sich Java gegen Java ausspricht, obwohl es für eine Sprache nützlich ist.

Die Tatsache, dass Sie "System.gc" nicht vertrauen können, um etwas zu tun, ist unglaublich entmutigend und kann leicht das Gefühl "Angst, Unsicherheit, Zweifel" für die Sprache hervorrufen.

In vielen Fällen ist es hilfreich, mit Speicherspitzen umzugehen, die Sie absichtlich verursachen, bevor ein wichtiges Ereignis eintritt. Dies würde dazu führen, dass Benutzer glauben, Ihr Programm sei schlecht gestaltet / reagiert nicht.

Die Fähigkeit, die Speicherbereinigung zu steuern, wäre ein großartiges Schulungsinstrument, um das Verständnis der Menschen für die Funktionsweise der Speicherbereinigung zu verbessern und Programme dazu zu bringen, das Standardverhalten sowie das kontrollierte Verhalten auszunutzen.

Lassen Sie mich die Argumente dieses Threads überprüfen.

  1. Es ist ineffizient:

Oft macht das Programm nichts und Sie wissen, dass es aufgrund der Art und Weise, wie es entworfen wurde, nichts tut. Zum Beispiel kann es sein, dass es eine lange Wartezeit mit einem großen Wartemeldungsfeld gibt, und am Ende kann es auch einen Aufruf zum Sammeln von Müll hinzufügen, da die Zeit zum Ausführen einen sehr kleinen Bruchteil der Zeit in Anspruch nimmt langes Warten, aber verhindert, dass gc mitten in einer wichtigeren Operation agiert.

  1. Es ist immer eine schlechte Praxis und weist auf fehlerhaften Code hin.

Ich bin anderer Meinung, es spielt keine Rolle, welchen Müllsammler Sie haben. Seine Aufgabe ist es, Müll aufzuspüren und zu reinigen.

Indem Sie den gc in Zeiten aufrufen, in denen die Nutzung weniger kritisch ist, verringern Sie die Wahrscheinlichkeit, dass er ausgeführt wird, wenn Ihr Leben von dem spezifischen Code abhängt, der ausgeführt wird. Stattdessen wird entschieden, Müll zu sammeln.

Sicher, es verhält sich möglicherweise nicht so, wie Sie es möchten oder erwarten, aber wenn Sie es aufrufen möchten, wissen Sie, dass nichts passiert, und der Benutzer ist bereit, Langsamkeit / Ausfallzeiten zu tolerieren. Wenn das System.gc funktioniert, großartig! Wenn nicht, haben Sie es zumindest versucht. Es gibt einfach keine Nachteile, es sei denn, der Garbage Collector hat inhärente Nebenwirkungen, die etwas schrecklich Unerwartetes bewirken, wie sich ein Garbage Collector verhalten soll, wenn er manuell aufgerufen wird, und dies allein verursacht Misstrauen.

  1. Es ist kein häufiger Anwendungsfall:

Dies ist ein Anwendungsfall, der nicht zuverlässig erreicht werden kann, aber möglicherweise, wenn das System so konzipiert wurde. Es ist so, als würde man eine Ampel erstellen und so gestalten, dass einige / alle Ampeltasten nichts tun. Man fragt sich, warum die Taste zunächst vorhanden ist. Javascript hat keine Speicherbereinigungsfunktion, also ziehen wir an prüfe es nicht so sehr dafür.

  1. Die Spezifikation besagt, dass System.gc () ein Hinweis ist, dass GC ausgeführt werden sollte und die VM es ignorieren kann.

Was ist ein "Hinweis"? Was ist "ignorieren"? Ein Computer kann nicht einfach Hinweise nehmen oder etwas ignorieren. Es gibt strenge Verhaltenspfade, die dynamisch sein können und von der Absicht des Systems geleitet werden. Eine richtige Antwort würde beinhalten, was der Garbage Collector auf Implementierungsebene tatsächlich tut, was dazu führt, dass er keine Erfassung durchführt, wenn Sie ihn anfordern. Ist die Funktion einfach ein Nein? Gibt es irgendwelche Bedingungen, die ich erfüllen muss? Was sind diese Bedingungen?

So wie es aussieht, scheint Javas GC oft ein Monster zu sein, dem man einfach nicht vertraut. Sie wissen nicht, wann es kommen oder gehen wird, Sie wissen nicht, was es tun wird, wie es es tun wird. Ich kann mir vorstellen, dass einige Experten eine bessere Vorstellung davon haben, wie ihre Garbage Collection pro Anweisung funktioniert, aber die große Mehrheit hofft einfach, dass sie "nur funktioniert", und es ist frustrierend, einem undurchsichtig wirkenden Algorithmus vertrauen zu müssen, um für Sie zu arbeiten.

Es gibt eine große Lücke zwischen dem Lesen über etwas oder dem Unterrichten von etwas und dem tatsächlichen Erkennen der Implementierung, der Unterschiede zwischen den Systemen und der Möglichkeit, damit zu spielen, ohne sich den Quellcode ansehen zu müssen. Dies schafft Vertrauen und ein Gefühl der Meisterschaft / des Verständnisses / der Kontrolle.

Zusammenfassend gibt es ein inhärentes Problem mit den Antworten: "Diese Funktion kann möglicherweise nichts bewirken, und ich werde nicht näher darauf eingehen, wie zu erkennen ist, wann sie etwas tut und wann nicht und warum sie nicht oder wird." oft impliziert dies, dass es einfach gegen die Philosophie verstößt, dies zu versuchen, auch wenn die Absicht dahinter vernünftig ist ".

Es mag für Java GC in Ordnung sein, sich so zu verhalten, wie es ist, oder auch nicht, aber um es zu verstehen, ist es schwierig, wirklich zu verfolgen, in welche Richtung Sie gehen müssen, um einen umfassenden Überblick darüber zu erhalten, was Sie dem GC anvertrauen können und nicht zu tun, deshalb ist es zu einfach, der Sprache einfach zu misstrauen, weil der Zweck einer Sprache darin besteht, das Verhalten bis zu einem philosophischen Ausmaß zu kontrollieren (es ist für einen Programmierer, insbesondere für Anfänger, leicht, aufgrund bestimmter System- / Sprachverhalten in eine existenzielle Krise zu geraten) sind in der Lage zu tolerieren (und wenn Sie nicht können, werden Sie die Sprache erst verwenden, wenn Sie müssen), und mehr Dinge, die Sie nicht ohne bekannten Grund kontrollieren können, warum Sie sie nicht kontrollieren können, sind von Natur aus schädlich.


9

Die GC-Effizienz hängt von einer Reihe von Heuristiken ab. Eine häufig verwendete Heuristik ist beispielsweise, dass Schreibzugriffe auf Objekte normalerweise für Objekte erfolgen, die vor nicht allzu langer Zeit erstellt wurden. Ein weiterer Grund ist, dass viele Objekte sehr kurzlebig sind (einige Objekte werden für eine lange Zeit verwendet, aber viele werden einige Mikrosekunden nach ihrer Erstellung verworfen).

Anrufen System.gc()ist wie das Treten des GC. Es bedeutet: "all diese sorgfältig abgestimmten Parameter, diese intelligenten Organisationen, all die Anstrengungen, die Sie nur in die Zuordnung und Verwaltung der Objekte gesteckt haben, damit die Dinge reibungslos und gut ablaufen, einfach die ganze Menge fallen lassen und von vorne anfangen". Es kann die Leistung verbessern, aber meistens verschlechtert es nur die Leistung.

Um System.gc()zuverlässig (*) zu verwenden, müssen Sie wissen, wie der GC in all seinen Details funktioniert. Solche Details ändern sich in der Regel erheblich, wenn Sie eine JVM eines anderen Anbieters oder die nächste Version desselben Anbieters oder derselben JVM verwenden, jedoch mit geringfügig unterschiedlichen Befehlszeilenoptionen. Daher ist es selten eine gute Idee, es sei denn, Sie möchten ein bestimmtes Problem ansprechen, bei dem Sie alle diese Parameter steuern. Daher der Begriff "schlechte Praxis": Das ist nicht verboten, die Methode existiert, aber sie zahlt sich selten aus.

(*) Ich spreche hier von Effizienz. System.gc()wird nie brechen ein korrektes Java - Programm. Es wird auch keinen zusätzlichen Speicher heraufbeschwören, den die JVM sonst nicht hätte erhalten können: Vor dem Werfen eines OutOfMemoryErrorerledigt die JVM die Aufgabe System.gc(), auch wenn dies ein letzter Ausweg ist.


1
+1 für die Erwähnung, dass System.gc () OutOfMemoryError nicht verhindert. Einige Leute glauben das.
Sleske

1
Tatsächlich kann OutOfMemoryError aufgrund der Behandlung von weichen Referenzen verhindert werden. Die nach dem letzten GC-Lauf erstellten SoftReferences werden in der mir bekannten Implementierung nicht erfasst. Dies ist jedoch ein Implementierungsdetail, das jederzeit geändert werden kann, eine Art Fehler und nichts, worauf Sie sich verlassen sollten.
Maaartinus

8

Manchmal ( nicht oft! ) Wissen Sie wirklich mehr über vergangene, aktuelle und zukünftige Speichernutzung als über die Laufzeit. Dies kommt nicht sehr oft vor, und ich würde behaupten, niemals in einer Webanwendung zu sein, während normale Seiten bereitgestellt werden.

Vor vielen Jahren arbeite ich an einem Berichtsgenerator

  • Hatte einen einzigen Thread
  • Lesen Sie die "Berichtsanforderung" aus einer Warteschlange
  • Die für den Bericht benötigten Daten wurden aus der Datenbank geladen
  • Erstellte den Bericht und schickte ihn per E-Mail.
  • Für immer wiederholt, schlafend, wenn keine offenen Anfragen vorhanden waren.
  • Es wurden keine Daten zwischen Berichten wiederverwendet und keine Einzahlungen vorgenommen.

Erstens, da es nicht in Echtzeit war und die Benutzer erwarteten, auf einen Bericht zu warten, war eine Verzögerung während des GC-Laufs kein Problem, aber wir mussten Berichte mit einer Geschwindigkeit erstellen, die schneller war als angefordert.

Wenn man sich den obigen Überblick über den Prozess ansieht, ist klar, dass.

  • Wir wissen, dass es unmittelbar nach dem Versenden eines Berichts nur sehr wenige Live-Objekte geben würde, da die nächste Anfrage noch nicht bearbeitet wurde.
  • Es ist bekannt, dass die Kosten für die Ausführung eines Speicherbereinigungszyklus von der Anzahl der lebenden Objekte abhängen. Die Menge an Speicherplatz hat nur geringe Auswirkungen auf die Kosten eines GC-Laufs.
  • Wenn die Warteschlange leer ist, gibt es nichts Besseres, als den GC auszuführen.

Daher hat es sich eindeutig gelohnt, einen GC-Lauf durchzuführen, wenn die Anforderungswarteschlange leer war. Das hatte keinen Nachteil.

Es kann sich lohnen, einen GC-Lauf durchzuführen, nachdem jeder Bericht per E-Mail gesendet wurde, da wir wissen, dass dies ein guter Zeitpunkt für einen GC-Lauf ist. Wenn der Computer jedoch über genügend RAM verfügt, werden bessere Ergebnisse erzielt, wenn der GC-Lauf verzögert wird.

Dieses Verhalten wurde auf Basis der einzelnen Installationen konfiguriert. Einige Kunden haben nach jedem Bericht einen erzwungenen GC aktiviert, wodurch der Schutz von Berichten erheblich beschleunigt wurde. (Ich gehe davon aus, dass dies auf den geringen Arbeitsspeicher auf dem Server zurückzuführen ist und viele andere Prozesse ausgeführt werden. Daher hat eine zeitlich erzwungene GC das Paging reduziert.)

Wir haben nie festgestellt, dass eine Installation, die nicht von Vorteil war, jedes Mal, wenn die Arbeitswarteschlange leer war, ein erzwungener GC-Lauf war.

Aber lassen Sie uns klar sein, das oben Genannte ist kein häufiger Fall.


3

Vielleicht schreibe ich beschissenen Code, aber mir ist klar geworden, dass das Klicken auf das Papierkorbsymbol bei Eclipse- und Netbeans-IDEs eine gute Vorgehensweise ist.


1
Das kann so sein. Wenn Eclipse oder NetBeans jedoch so programmiert wurden, dass sie System.gc()regelmäßig aufgerufen werden, ist das Verhalten wahrscheinlich ärgerlich.
Stephen C

2

Erstens gibt es einen Unterschied zwischen Spezifikation und Realität. Die Spezifikation besagt, dass System.gc () ein Hinweis ist, dass GC ausgeführt werden sollte und die VM es ignorieren kann. Die Realität ist, dass die VM einen Aufruf von System.gc () niemals ignoriert.

Das Anrufen von GC ist mit einem nicht trivialen Aufwand verbunden. Wenn Sie dies zu einem zufälligen Zeitpunkt tun, werden Sie wahrscheinlich keine Belohnung für Ihre Bemühungen sehen. Andererseits ist es sehr wahrscheinlich, dass eine natürlich ausgelöste Sammlung die Kosten des Anrufs wieder wettmacht. Wenn Sie Informationen haben, die darauf hinweisen, dass ein GC ausgeführt werden soll, können Sie System.gc () aufrufen, und Sie sollten die Vorteile sehen. Ich habe jedoch die Erfahrung gemacht, dass dies nur in wenigen Fällen der Fall ist, da es sehr unwahrscheinlich ist, dass Sie über genügend Informationen verfügen, um zu verstehen, ob und wann System.gc () aufgerufen werden sollte.

Ein hier aufgeführtes Beispiel, das auf die Mülltonne in Ihrer IDE trifft. Wenn Sie zu einem Meeting gehen, treffen Sie es doch. Der Overhead wird Sie nicht beeinträchtigen und der Haufen wird möglicherweise aufgeräumt, wenn Sie zurückkommen. Tun Sie dies in einem Produktionssystem, und häufige Anrufe zum Sammeln bringen es zum Erliegen! Selbst gelegentliche Anrufe wie die von RMI können die Leistung beeinträchtigen.


3
"Die Realität ist, dass die VM einen Aufruf von System.gc () niemals ignoriert." - Falsch. Lesen Sie mehr über -XX: -DisableExplicitGC.
Stephen C

1

Ja, das Aufrufen von System.gc () garantiert nicht, dass es ausgeführt wird. Es handelt sich um eine Anforderung an die JVM, die möglicherweise ignoriert wird. Aus den Dokumenten:

Das Aufrufen der gc-Methode legt nahe, dass die Java Virtual Machine Anstrengungen unternimmt, um nicht verwendete Objekte zu recyceln

Es ist fast immer eine schlechte Idee, es zu nennen, da die automatische Speicherverwaltung normalerweise besser als Sie weiß, wann Sie gc verwenden müssen. Dies geschieht, wenn der interne Pool an freiem Speicher niedrig ist oder wenn das Betriebssystem die Rückgabe von Speicher anfordert.

Es kann akzeptabel sein, System.gc () aufzurufen, wenn Sie wissen, dass dies hilfreich ist . Damit meine ich, dass Sie das Verhalten beider Szenarien auf der Bereitstellungsplattform gründlich getestet und gemessen haben , und Sie können zeigen, dass es hilfreich ist. Beachten Sie jedoch, dass der GC nicht leicht vorhersehbar ist - er kann bei einem Lauf helfen und bei einem anderen weh tun.


<stroke> Aber auch aus dem Javadoc: _Wenn das Steuerelement vom Methodenaufruf zurückkehrt, hat die virtuelle Maschine nach besten Kräften versucht, alle verworfenen Objekte zu recyceln > Scheiß drauf, es gibt einen Fehlerbericht darüber, dass es irreführend ist. Ab was weiß man besser, was schadet es, die JVM anzudeuten?
Zneak

1
Der Schaden ist, dass das Sammeln zum falschen Zeitpunkt eine enorme Verlangsamung bedeuten kann. Der Hinweis, den Sie geben, ist wahrscheinlich ein schlechter. Probieren Sie den Kommentar "Best Effort" aus und sehen Sie ihn in einem Tool wie JConsole. Manchmal macht das Klicken auf die Schaltfläche "GC durchführen" nichts
Tom

Es tut uns leid, nicht zuzustimmen, aber das Aufrufen von System.gc () in OpenJDK und alles, was darauf basiert (z. B. HP), führt immer zu einem Speicherbereinigungszyklus. Tatsächlich scheint dies auch für die J9-Implementierung von IBM zu gelten
Kirk,

@Kirk - Falsch: google und lies über -XX: -DisableExplicitGC.
Stephen C

0

Nach meiner Erfahrung ist die Verwendung von System.gc () effektiv eine plattformspezifische Form der Optimierung (wobei "Plattform" die Kombination aus Hardwarearchitektur, Betriebssystem, JVM-Version und möglicherweise mehr Laufzeitparametern wie verfügbarem RAM ist), da deren Verhalten Während sie auf einer bestimmten Plattform grob vorhersehbar sind, können (und werden) sie zwischen den Plattformen erheblich variieren.

Ja, es gibt Situationen, in denen System.gc () die (wahrgenommene) Leistung verbessert. Ein Beispiel ist, wenn Verzögerungen in einigen Teilen Ihrer App tolerierbar sind, in anderen jedoch nicht (das oben genannte Spielbeispiel, bei dem GC zu Beginn eines Levels und nicht während des Levels stattfinden soll).

Ob es hilft oder schadet (oder nichts tut), hängt jedoch stark von der Plattform ab (wie oben definiert).

Ich denke, es ist als plattformspezifische Optimierung der letzten Instanz gültig (dh wenn andere Leistungsoptimierungen nicht ausreichen). Aber Sie sollten es niemals nennen, nur weil Sie glauben, dass es helfen könnte (ohne bestimmte Benchmarks), weil es wahrscheinlich nicht hilft.


0
  1. Da Objekte mithilfe des neuen Operators dynamisch zugewiesen
    werden, fragen Sie sich möglicherweise, wie solche Objekte zerstört und ihr
    Speicher für eine spätere Neuzuweisung freigegeben werden.

  2. In einigen Sprachen, wie z. B. C ++, müssen dynamisch zugewiesene Objekte mithilfe eines Löschoperators manuell freigegeben werden.

  3. Java verfolgt einen anderen Ansatz. Die Freigabe wird automatisch für Sie erledigt.
  4. Die Technik, die dies erreicht, wird als Speicherbereinigung bezeichnet. Dies funktioniert folgendermaßen: Wenn keine Verweise auf ein Objekt vorhanden sind, wird angenommen, dass dieses Objekt nicht mehr benötigt wird, und der vom Objekt belegte Speicher kann zurückgefordert werden. Es besteht keine explizite Notwendigkeit, Objekte wie in C ++ zu zerstören.
  5. Die Speicherbereinigung erfolgt nur sporadisch (wenn überhaupt) während der Ausführung Ihres Programms.
  6. Es tritt nicht einfach auf, weil ein oder mehrere Objekte vorhanden sind, die nicht mehr verwendet werden.
  7. Darüber hinaus verwenden verschiedene Java-Laufzeitimplementierungen unterschiedliche Ansätze für die Speicherbereinigung, aber zum größten Teil sollten Sie beim Schreiben Ihrer Programme nicht darüber nachdenken müssen.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.