Verbessert die Verwendung von final für Variablen in Java die Speicherbereinigung?


86

Heute diskutieren meine Kollegen und ich über die Verwendung des finalSchlüsselworts in Java, um die Speicherbereinigung zu verbessern.

Zum Beispiel, wenn Sie eine Methode schreiben wie:

public Double doCalc(final Double value)
{
   final Double maxWeight = 1000.0;
   final Double totalWeight = maxWeight * value;
   return totalWeight;  
}

Das Deklarieren der Variablen in der Methode finalwürde der Garbage Collection helfen, den Speicher nach dem Beenden der Methode von den nicht verwendeten Variablen in der Methode zu bereinigen.

Ist das wahr?


Hier gibt es eigentlich zwei Dinge. 1) wenn Sie in ein lokales Methodenfeld und eine Instanz schreiben . Wenn Sie in eine Instanz schreiben, kann dies einen Vorteil haben.
Eugene

Antworten:


86

Hier ist ein etwas anderes Beispiel, eines mit Feldern vom endgültigen Referenztyp anstelle von lokalen Variablen vom endgültigen Werttyp:

public class MyClass {

   public final MyOtherObject obj;

}

Jedes Mal, wenn Sie eine Instanz von MyClass erstellen, erstellen Sie einen ausgehenden Verweis auf eine MyOtherObject-Instanz, und der GC muss diesem Link folgen, um nach Live-Objekten zu suchen.

Die JVM verwendet einen Mark-Sweep-GC-Algorithmus, der alle Live-Referenzen an den GC- "Root" -Standorten untersuchen muss (wie alle Objekte im aktuellen Aufrufstapel). Jedes lebende Objekt wird als lebendig "markiert", und jedes Objekt, auf das sich ein lebendes Objekt bezieht, wird ebenfalls als lebendig markiert.

Nach Abschluss der Markierungsphase durchläuft der GC den Heap und gibt Speicher für alle nicht markierten Objekte frei (und komprimiert den Speicher für die verbleibenden Live-Objekte).

Es ist auch wichtig zu erkennen, dass der Java-Heapspeicher in eine "junge Generation" und eine "alte Generation" unterteilt ist. Alle Objekte werden zunächst in der jungen Generation zugeordnet (manchmal auch als "Kindergarten" bezeichnet). Da die meisten Objekte nur von kurzer Dauer sind, ist der GC aggressiver, wenn es darum geht, den jüngsten Müll von der jungen Generation zu befreien. Wenn ein Objekt einen Sammlungszyklus der jungen Generation überlebt, wird es in die alte Generation (manchmal als "Tenured Generation" bezeichnet) verschoben, die weniger häufig verarbeitet wird.

Ich werde also auf den ersten Blick sagen: "Nein, der 'letzte' Modifikator hilft dem GC nicht, seine Arbeitsbelastung zu reduzieren."

Meiner Meinung nach besteht die beste Strategie zur Optimierung Ihrer Speicherverwaltung in Java darin, falsche Referenzen so schnell wie möglich zu beseitigen. Sie können dies tun, indem Sie einer Objektreferenz "null" zuweisen, sobald Sie damit fertig sind.

Oder, noch besser, minimieren Sie die Größe jedes Deklarationsbereichs. Wenn Sie beispielsweise ein Objekt am Anfang einer 1000-Zeilen-Methode deklarieren und das Objekt bis zum Ende des Bereichs dieser Methode (der letzten schließenden geschweiften Klammer) am Leben bleibt, bleibt das Objekt möglicherweise viel länger am Leben als tatsächlich notwendig.

Wenn Sie kleine Methoden mit nur etwa einem Dutzend Codezeilen verwenden, fallen die in dieser Methode deklarierten Objekte schneller aus dem Geltungsbereich und der GC kann den größten Teil seiner Arbeit innerhalb der wesentlich effizienteren erledigen junge Generation. Sie möchten nicht, dass Objekte in die ältere Generation verschoben werden, es sei denn, dies ist unbedingt erforderlich.


Denkanstöße. Ich dachte immer, Inline-Code sei schneller, aber wenn jvm nicht genügend Speicher hat, wird er auch langsamer. hmmmmm ...
WolfmanDragon

1
Ich rate hier nur ... aber ich gehe davon aus, dass der JIT-Compiler endgültige primitive Werte (keine Objekte) für bescheidene Leistungssteigerungen einbinden kann. Code-Inlining hingegen kann zwar erhebliche Optimierungen bewirken, hat jedoch nichts mit endgültigen Variablen zu tun.
Benjaminism

2
Es wäre nicht möglich, einem bereits erstellten endgültigen Objekt null zuzuweisen, dann könnte es möglicherweise schwieriger werden, wenn es endgültig ist, anstatt zu helfen
Hernán Eche

1
Man könnte {} verwenden, um den Gültigkeitsbereich auch in einer großen Methode einzuschränken, anstatt ihn auf mehrere private Methoden aufzuteilen, die für andere Klassenmethoden möglicherweise nicht relevant sind.
mmm

Sie können auch lokale Variablen anstelle von Feldern verwenden, wenn dies möglich ist, um die Speicherbereinigung zu verbessern und referenzielle Bindungen zu verringern.
Sivi

37

Das Deklarieren einer lokalen Variablen finalhat keine Auswirkungen auf die Speicherbereinigung. Dies bedeutet nur, dass Sie die Variable nicht ändern können. Ihr Beispiel oben sollte nicht kompiliert werden, da Sie totalWeightdie markierte Variable ändern final. Wenn Sie jedoch ein Grundelement ( doubleanstelle von Double) deklarieren, finalkann diese Variable in den aufrufenden Code eingefügt werden, was zu einer Verbesserung des Speichers und der Leistung führen kann. Dies wird verwendet, wenn Sie eine Anzahl von public static final Stringsin einer Klasse haben.

Im Allgemeinen optimieren der Compiler und die Laufzeit, wo dies möglich ist. Es ist am besten, den Code entsprechend zu schreiben und nicht zu knifflig zu sein. Verwenden finalSie diese Option, wenn die Variable nicht geändert werden soll. Angenommen, der Compiler führt einfache Optimierungen durch. Wenn Sie sich Sorgen über die Leistung oder die Speichernutzung machen, verwenden Sie einen Profiler, um das eigentliche Problem zu ermitteln.


26

Nein, es ist ausdrücklich nicht wahr.

Denken Sie daran, dass finaldies keine Konstante bedeutet, sondern nur, dass Sie die Referenz nicht ändern können.

final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.

Es kann einige kleine Optimierungen geben, die auf dem Wissen beruhen, dass die JVM die Referenz niemals ändern muss (z. B. keine Überprüfung, ob sie sich geändert hat), aber sie wäre so geringfügig, dass sie sich keine Sorgen macht.

Final sollte als nützliche Metadaten für den Entwickler und nicht als Compileroptimierung betrachtet werden.


17

Einige Punkte, die geklärt werden müssen:

  • Das Nullstellen der Referenz sollte GC nicht helfen. Wenn dies der Fall wäre, würde dies darauf hinweisen, dass Ihre Variablen einen übermäßigen Gültigkeitsbereich haben. Eine Ausnahme bildet der Fall von Objektvetternwirtschaft.

  • In Java gibt es noch keine On-Stack-Zuordnung.

  • Wenn Sie eine Variable final deklarieren, können Sie dieser Variablen (unter normalen Bedingungen) keinen neuen Wert zuweisen. Da final nichts über den Umfang aussagt, sagt es nichts über die Auswirkungen auf die GC aus.


In Java gibt es eine On-Stack-Zuweisung (von Grundelementen und Verweisen auf Objekte im Heap): stackoverflow.com/a/8061692/32453 Java erfordert, dass Objekte in derselben Schließung wie eine anonyme Klasse / Lambda ebenfalls endgültig sind, dreht sich jedoch um Das ist nur, um "Speicherbedarf / Frame" / Verwirrung zu reduzieren, damit es nichts damit zu tun hat, dass es gesammelt wird ...
Rogerdpack

11

Nun, ich weiß nicht, wie der "letzte" Modifikator in diesem Fall verwendet wird oder wie er sich auf den GC auswirkt.

Aber ich kann Ihnen Folgendes sagen: Ihre Verwendung von Boxed-Werten anstelle von Grundelementen (z. B. Double anstelle von Double) weist diese Objekte eher auf dem Heap als auf dem Stapel zu und erzeugt unnötigen Müll, den der GC bereinigen muss.

Ich verwende Boxed-Primitive nur, wenn dies für eine vorhandene API erforderlich ist oder wenn ich nullfähige Primitive benötige.


1
Du hast recht. Ich brauchte nur ein kurzes Beispiel, um meine Frage zu erklären.
Goran Martinic

5

Endgültige Variablen können nach der ersten Zuweisung nicht mehr geändert werden (vom Compiler erzwungen).

Dies ändert nichts am Verhalten der Garbage Collection als solcher. Das einzige ist, dass diese Variablen nicht auf Null gesetzt werden können, wenn sie nicht mehr verwendet werden (was der Speicherbereinigung in speichersicheren Situationen helfen kann).

Sie sollten wissen, dass der Compiler mit final Annahmen darüber treffen kann, was optimiert werden soll. Inlining-Code und ohne Code, von dem bekannt ist, dass er nicht erreichbar ist.

final boolean debug = false;

......

if (debug) {
  System.out.println("DEBUG INFO!");
}

Der Ausdruck wird nicht in den Bytecode aufgenommen.


@Eugene Hängt von Ihrem Sicherheitsmanager ab und davon, ob der Compiler die Variable eingebunden hat oder nicht.
Thorbjørn Ravn Andersen

richtig, ich war nur pedantisch; nichts mehr; gab auch eine Antwort
Eugene

4

Es gibt einen nicht so bekannten Eckfall mit Müllsammlern der Generation. (Für eine kurze Beschreibung lesen Sie die Antwort von Benjaminism für einen tieferen Einblick lesen Sie die Artikel am Ende).

Die Idee in Generations-GCs ist, dass meistens nur junge Generationen berücksichtigt werden müssen. Der Stammort wird nach Referenzen durchsucht, und dann werden die Objekte der jungen Generation gescannt. Während dieser häufigeren Sweeps wird kein Objekt der alten Generation überprüft.

Das Problem ergibt sich nun aus der Tatsache, dass ein Objekt keine Verweise auf jüngere Objekte haben darf. Wenn ein langlebiges Objekt (alte Generation) einen Verweis auf ein neues Objekt erhält, muss dieser Verweis vom Garbage Collector explizit verfolgt werden (siehe Artikel von IBM zum Hotspot-JVM-Collector ), was sich tatsächlich auf die GC-Leistung auswirkt.

Der Grund, warum ein altes Objekt nicht auf ein jüngeres Objekt verweisen kann, besteht darin, dass das alte Objekt, da es in kleineren Sammlungen nicht überprüft wird, nicht markiert wird und falsch ist, wenn der einzige Verweis auf das Objekt im alten Objekt beibehalten wird während der Sweep-Phase freigegeben.

Wie viele darauf hinweisen, wirkt sich das endgültige Schlüsselwort natürlich nicht wirklich auf den Garbage Collector aus, garantiert jedoch, dass die Referenz niemals in ein jüngeres Objekt geändert wird, wenn dieses Objekt die kleineren Sammlungen überlebt und auf den älteren Heap gelangt.

Artikel:

IBM zur Speicherbereinigung: Verlauf , Hotspot-JVM und Leistung . Diese sind möglicherweise nicht mehr vollständig gültig, da sie aus dem Jahr 2003/04 stammen, bieten jedoch einen leicht lesbaren Einblick in GCs.

Sun on Tuning Müllabfuhr


3

GC wirkt auf nicht erreichbare Refs. Dies hat nichts mit "final" zu tun, was lediglich eine Behauptung einer einmaligen Zuordnung ist. Ist es möglich, dass der GC einiger VMs "final" verwenden kann? Ich verstehe nicht wie oder warum.


3

finalInformationen zu lokalen Variablen und Parametern haben keinen Einfluss auf die erstellten Klassendateien und können daher die Laufzeitleistung nicht beeinträchtigen. Wenn eine Klasse keine Unterklassen hat, behandelt HotSpot diese Klasse sowieso als endgültig (sie kann später rückgängig gemacht werden, wenn eine Klasse geladen wird, die diese Annahme verletzt). Ich glaube, dass finalMethoden den Klassen sehr ähnlich sind. finalIm statischen Feld kann die Variable möglicherweise als "Konstante zur Kompilierungszeit" interpretiert und auf dieser Basis von javac optimiert werden. finalAuf Feldern kann die JVM einige Freiheiten ignorieren , bevor Beziehungen auftreten.


2

Es scheint viele Antworten zu geben, die wandernde Vermutungen sind. Die Wahrheit ist, dass es keinen endgültigen Modifikator für lokale Variablen auf Bytecode-Ebene gibt. Die virtuelle Maschine wird niemals erfahren, dass Ihre lokalen Variablen als endgültig definiert wurden oder nicht.

Die Antwort auf Ihre Frage ist ein klares Nein.


Das mag wahr sein, aber der Compiler kann die endgültigen Informationen während der Datenflussanalyse weiterhin verwenden.

@WernerVanBelle Der Compiler weiß bereits, dass eine Variable nur einmal gesetzt wird. Es muss bereits eine Datenflussanalyse durchgeführt werden, um zu wissen, dass eine Variable möglicherweise null ist, vor der Verwendung nicht initialisiert wird usw. Daher bieten lokale Finals dem Compiler keine neuen Informationen.
Matt Quigley

Das tut es nicht. Die Datenflussanalyse kann viele Dinge ableiten, aber es ist möglich, ein vollständiges Programm in einem Block zu haben, das eine lokale Variable setzt oder nicht. Der Compiler kann nicht im Voraus wissen, ob die Variable geschrieben wird und überhaupt konstant ist. Daher kann der Compiler ohne das Schlüsselwort final nicht garantieren, dass eine Variable final ist oder nicht.

@WernerVanBelle Ich bin wirklich fasziniert, können Sie ein Beispiel geben? Ich sehe nicht ein, wie es eine endgültige Zuordnung zu einer nicht endgültigen Variablen geben kann, von der der Compiler nichts weiß. Der Compiler weiß, dass Sie eine nicht initialisierte Variable nicht verwenden können. Wenn Sie versuchen, eine endgültige Variable innerhalb einer Schleife zuzuweisen, lässt Sie der Compiler nicht zu. Was ist ein Beispiel, in dem eine Variable, die als final deklariert werden kann, aber NICHT ist und der Compiler nicht garantieren kann, dass sie final ist? Ich vermute, dass jedes Beispiel eine Variable wäre, die überhaupt nicht als endgültig deklariert werden könnte.
Matt Quigley

Ah, nachdem ich Ihren Kommentar erneut gelesen habe, sehe ich, dass Ihr Beispiel eine Variable ist, die in einem bedingten Block initialisiert wurde. Diese Variablen können nicht endgültig sein, es sei denn, alle Bedingungspfade initialisieren die Variable einmal. Der Compiler kennt solche Deklarationen also - so können Sie eine endgültige Variable kompilieren, die an zwei verschiedenen Stellen initialisiert wurde (think final int x; if (cond) x=1; else x=2;). Der Compiler kann daher ohne das Schlüsselwort final garantieren, dass eine Variable endgültig ist oder nicht.
Matt Quigley

1

Alle Methoden und Variablen können standardmäßig in Unterklassen überschrieben werden. Wenn wir die Unterklassen vor dem Überschreiben der Mitglieder der Oberklasse schützen möchten, können wir sie mit dem Schlüsselwort final als final deklarieren. Wenn Sie beispielsweise final int a=10; final void display(){......} eine Methode endgültig festlegen, wird sichergestellt, dass die in der Oberklasse definierte Funktionalität ohnehin nie geändert wird. Ebenso kann der Wert einer endgültigen Variablen niemals geändert werden. Endvariablen verhalten sich wie Klassenvariablen.


1

Genau genommen über Instanz Felder, final könnte die Leistung etwas verbessern , wenn eine bestimmte GC will das ausnutzen. Wenn ein gleichzeitiger GCVorgang stattfindet (das bedeutet, dass Ihre Anwendung noch ausgeführt wird, während der GC ausgeführt wird ), finden Sie hier eine ausführlichere Erklärung . GCs müssen bestimmte Barrieren anwenden, wenn Schreib- und / oder Lesevorgänge ausgeführt werden. Der Link, den ich Ihnen gegeben habe, erklärt das ziemlich genau, aber um es wirklich kurz zu machen: Wenn a GCgleichzeitig arbeitet, werden alle Lese- und Schreibvorgänge auf dem Heap (während dieser GC ausgeführt wird) "abgefangen" und später angewendet. damit die gleichzeitige GC-Phase ihre Arbeit beenden kann.

Zum finalBeispiel können Felder weggelassen werden, da sie nicht geändert werden können (außer Reflexion). Und das ist nicht nur reine Theorie.

Shenandoah GChat sie in der Praxis (wenn auch nicht lange ), und Sie können zum Beispiel tun:

-XX:+UnlockExperimentalVMOptions  
-XX:+UseShenandoahGC  
-XX:+ShenandoahOptimizeInstanceFinals

Und es wird Optimierungen im GC-Algorithmus geben, die ihn etwas schneller machen. Dies liegt daran, dass es keine Hindernisse geben wird, die abfangen final, da niemand sie jemals modifizieren sollte. Nicht einmal über Reflexion oder JNI.


0

Das einzige, was mir einfällt, ist, dass der Compiler die endgültigen Variablen möglicherweise optimiert und sie als Konstanten in den Code einfügt, sodass Ihnen am Ende kein Speicher zugewiesen wird.


0

Absolut, solange die Lebensdauer des Objekts verkürzt wird, was einen großen Vorteil der Speicherverwaltung bietet, haben wir kürzlich die Exportfunktionalität mit Instanzvariablen für einen Test und einem anderen Test mit lokaler Variable auf Methodenebene untersucht. Während des Lasttests wirft JVM beim ersten Test einen Speicherfehler aus und JVM wurde angehalten. Im zweiten Test konnte der Bericht jedoch aufgrund einer besseren Speicherverwaltung erfolgreich abgerufen werden.


0

Ich ziehe es vor, lokale Variablen als endgültig zu deklarieren, wenn:

  • Ich muss sie endgültig machen, damit sie für eine anonyme Klasse freigegeben werden können (zum Beispiel: Erstellen eines Daemon-Threads und Ermöglichen des Zugriffs auf einen Wert aus der eingeschlossenen Methode)

  • Ich möchte sie endgültig machen (zum Beispiel: ein Wert, der nicht versehentlich überschrieben werden sollte / wird)

Helfen sie bei der schnellen Müllabfuhr?
AFAIK ein Objekt wird ein Kandidat für die GC-Sammlung, wenn es keine starken Verweise darauf hat, und auch in diesem Fall gibt es keine Garantie dafür, dass es sofort Müll gesammelt wird. Im Allgemeinen wird gesagt, dass eine starke Referenz stirbt, wenn sie den Gültigkeitsbereich verlässt oder der Benutzer sie explizit der Nullreferenz zuweist. Wenn Sie sie als endgültig deklarieren, bedeutet dies, dass die Referenz so lange bestehen bleibt, bis die Methode existiert (es sei denn, ihr Gültigkeitsbereich wird explizit auf eingegrenzt) ein bestimmter innerer Block {}), weil Sie die endgültigen Variablen nicht neu zuweisen können (dh nicht null neu zuweisen können). Also ich WRT Garbage Collection ‚final‘ denken kann eine unerwünschte mögliche Verzögerung einzuführen , so ein wenig vorsichtig sein müssen , da Umfang wie die Kontrollen bei der Definition , wenn sie Kandidaten für GC werden wird.

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.