Java (JVM) Memory Model – Speicherverwaltung in Java

Das Verständnis des JVM-Speichermodells, der Java-Speicherverwaltung ist sehr wichtig, wenn Sie das Funktionieren der Java-Garbage Collection verstehen möchten. Heute werden wir uns mit der Speicherverwaltung in Java, den verschiedenen Teilen des JVM-Speichers und der Überwachung sowie Durchführung der Feinabstimmung der Garbage Collection befassen.

Java (JVM)-Speichermodell

Wie Sie im obigen Bild sehen können, ist der JVM-Speicher in separate Teile unterteilt. Auf einer allgemeinen Ebene ist der JVM-Heap-Speicher physisch in zwei Teile unterteilt – Young Generation und Old Generation.

Speicherverwaltung in Java – Young Generation

Die Young Generation ist der Ort, an dem alle neuen Objekte erstellt werden. Wenn die Young Generation gefüllt ist, wird eine Garbage Collection durchgeführt. Diese Garbage Collection wird als Minor GC bezeichnet. Die Young Generation ist in drei Teile unterteilt – Eden Memory und zwei Survivor Memory-Räume. Wichtige Punkte zu den Young Generation Spaces:

  • Die meisten neu erstellten Objekte befinden sich im Eden-Speicherbereich.
  • Wenn der Eden-Speicher mit Objekten gefüllt ist, wird eine Minor-GC durchgeführt und alle überlebenden Objekte werden in einen der Überlebensbereiche verschoben.
  • Die Minor-GC überprüft auch die überlebenden Objekte und verschiebt sie in den anderen Überlebensbereich. Zu einem Zeitpunkt ist also immer einer der Überlebensbereiche leer.
  • Objekte, die nach vielen Zyklen der GC überleben, werden in den Speicherbereich der alten Generation verschoben. Normalerweise geschieht dies, indem ein Schwellenwert für das Alter der Objekte der jungen Generation festgelegt wird, bevor sie für die Beförderung in die alte Generation in Frage kommen.

Speicherverwaltung in Java – Alte Generation

Der Speicher der alten Generation enthält die Objekte, die lange leben und nach vielen Runden der Minor-GC überleben. Normalerweise wird eine Garbage Collection in der Speicher der alten Generation durchgeführt, wenn er voll ist. Die Garbage Collection der alten Generation wird als Major-GC bezeichnet und dauert normalerweise länger.

Stop-the-World-Ereignis

Alle Garbage Collections sind „Stop-the-World“-Ereignisse, weil alle Anwendungs-Threads angehalten werden, bis die Operation abgeschlossen ist. Da die Young-Generation kurzlebige Objekte enthält, ist die Minor-GC sehr schnell und die Anwendung wird davon nicht beeinträchtigt. Die Major-GC dauert jedoch lange, da sie alle aktiven Objekte überprüft. Die Major-GC sollte minimiert werden, da sie Ihre Anwendung während der Garbage-Collection-Dauer unresponsiv macht. Wenn Sie also eine reaktionsfähige Anwendung haben und viele Major-Garbage-Collection-Vorgänge stattfinden, werden Sie Timeout-Fehler bemerken. Die Dauer, die der Garbage Collector benötigt, hängt von der verwendeten Strategie für die Garbage Collection ab. Daher ist es notwendig, den Garbage Collector zu überwachen und anzupassen, um Timeouts in stark reaktionsfähigen Anwendungen zu vermeiden.

Java Memory Model – Permanent Generation

Die Permanent Generation oder „Perm Gen“ enthält die Anwendungs-Metadaten, die von der JVM benötigt werden, um die in der Anwendung verwendeten Klassen und Methoden zu beschreiben. Beachten Sie, dass Perm Gen nicht Teil des Java-Heap-Speichers ist. Perm Gen wird von der JVM zur Laufzeit basierend auf den von der Anwendung verwendeten Klassen befüllt. Perm Gen enthält auch Java-SE-Bibliotheksklassen und -methoden. Perm Gen-Objekte werden bei einer vollständigen Garbage Collection gesammelt.

Java-Speichermodell – Methodenbereich

Der Methodenbereich ist ein Teil des Speicherbereichs im Perm Gen und wird verwendet, um die Klassenstruktur (Laufzeitkonstanten und statische Variablen) sowie den Code für Methoden und Konstruktoren zu speichern.

Java-Speichermodell – Speicherpool

Speicherbecken werden von JVM-Speichermanagern erstellt, um einen Pool von unveränderlichen Objekten zu erzeugen, wenn die Implementierung dies unterstützt. Der String-Pool ist ein gutes Beispiel für diese Art von Speicherpool. Der Speicherpool kann je nach Implementierung des JVM-Speichermanagers zum Heap oder zum Perm Gen gehören.

Java-Speichermodell – Runtime-Konstantenpool

Der Laufzeitkonstantenpool ist eine pro-Klasse Laufzeitdarstellung des Konstantenpools in einer Klasse. Er enthält die Klassenlaufzeitkonstanten und statische Methoden. Der Laufzeitkonstantenpool ist Teil des Methodenbereichs.

Java-Speichermodell – Java-Stackspeicher

Der Java-Stackspeicher wird für die Ausführung eines Threads verwendet. Er enthält methodenspezifische Werte, die kurzlebig sind, sowie Verweise auf andere Objekte im Heap, die von der Methode referenziert werden. Sie sollten den Unterschied zwischen Stack- und Heap-Speicher lesen.

Speicherverwaltung in Java – Java-Heap-Speicherschalter

Java bietet eine Vielzahl von Speicherschaltern, die wir verwenden können, um die Speichergrößen und ihre Verhältnisse festzulegen. Einige der häufig verwendeten Speicherschalter sind:

VM Switch VM Switch Description
-Xms For setting the initial heap size when JVM starts
-Xmx For setting the maximum heap size.
-Xmn For setting the size of the Young Generation, rest of the space goes for Old Generation.
-XX:PermGen For setting the initial size of the Permanent Generation memory
-XX:MaxPermGen For setting the maximum size of Perm Gen
-XX:SurvivorRatio For providing ratio of Eden space and Survivor Space, for example if Young Generation size is 10m and VM switch is -XX:SurvivorRatio=2 then 5m will be reserved for Eden Space and 2.5m each for both the Survivor spaces. The default value is 8.
-XX:NewRatio For providing ratio of old/new generation sizes. The default value is 2.

Meistens sind die oben genannten Optionen ausreichend, aber wenn Sie auch andere Optionen überprüfen möchten, dann sehen Sie bitte auf der JVM-Options-Offizielle Seite nach.

Speicherverwaltung in Java – Java Garbage Collection

Die Java Garbage Collection ist der Prozess zur Identifizierung und Entfernung der nicht verwendeten Objekte aus dem Speicher und zur Freigabe von Speicherplatz für Objekte, die in der zukünftigen Verarbeitung erstellt werden. Eine der besten Funktionen der Java-Programmiersprache ist die automatische Garbage Collection, im Gegensatz zu anderen Programmiersprachen wie C, bei denen die Speicherzuweisung und -freigabe ein manueller Prozess ist. Der Garbage Collector ist das im Hintergrund laufende Programm, das alle Objekte im Speicher überprüft und Objekte ermittelt, auf die kein Teil des Programms verweist. Alle diese nicht referenzierten Objekte werden gelöscht, und Speicherplatz wird für die Zuweisung an andere Objekte zurückgewonnen. Eine der grundlegenden Methoden der Garbage Collection umfasst drei Schritte:

  1. Markierung: Dies ist der erste Schritt, bei dem der Garbage Collector erkennt, welche Objekte verwendet werden und welche nicht.
  2. Normales Löschen: Der Garbage Collector entfernt die nicht verwendeten Objekte und gibt den freien Speicherplatz frei, um ihn anderen Objekten zuzuweisen.
  3. Löschen mit Kompaktierung: Für bessere Leistung können nach dem Löschen nicht verwendeter Objekte alle überlebenden Objekte zusammen verschoben werden. Dies erhöht die Leistung der Speicherzuweisung für neuere Objekte.

Es gibt zwei Probleme mit einem einfachen Markierungs- und Löschansatz.

  1. Erstens ist es nicht effizient, weil die meisten neu erstellten Objekte ungenutzt bleiben werden.
  2. Zweitens sind Objekte, die für mehrere Garbage-Collection-Zyklen verwendet werden, höchstwahrscheinlich auch für zukünftige Zyklen in Verwendung.

Die oben genannten Mängel des einfachen Ansatzes sind der Grund dafür, dass die Java Garbage Collection generational ist und wir Young Generation und Old Generation Speicherbereiche im Heap-Speicher haben. Ich habe bereits oben erklärt, wie Objekte basierend auf dem Minor GC und Major GC von einem generationalen Raum in einen anderen gescannt und verschoben werden.

Speicherverwaltung in Java – Arten der Java Garbage Collection

Es gibt fünf Arten von Garbage-Collection-Typen, die wir in unseren Anwendungen verwenden können. Wir müssen einfach den JVM-Schalter verwenden, um die Garbage-Collection-Strategie für die Anwendung zu aktivieren. Lassen Sie uns jeden von ihnen nacheinander betrachten.

  1. Serial GC (-XX:+UseSerialGC): Der Serial GC verwendet den einfachen mark-sweep-compact-Ansatz für die Garbage Collection von jungen und alten Generationen, d.h. Minor und Major GC. Serial GC ist nützlich in Client-Maschinen wie unseren einfachen eigenständigen Anwendungen und Maschinen mit kleinerer CPU. Es ist gut für kleine Anwendungen mit geringem Speicherbedarf.
  2. Parallel GC (-XX:+UseParallelGC): Parallel GC ist dasselbe wie der Serielle GC, außer dass es N Threads für die Garbage Collection der jungen Generation erzeugt, wobei N die Anzahl der CPU-Kerne im System ist. Wir können die Anzahl der Threads mithilfe der -XX:ParallelGCThreads=n JVM-Option kontrollieren. Der Parallel Garbage Collector wird auch Durchsatz-Collector genannt, weil er mehrere CPUs verwendet, um die Leistung der GC zu verbessern. Parallel GC verwendet einen einzigen Thread für die Garbage Collection der alten Generation.
  3. Parallel Old GC (-XX:+UseParallelOldGC): Dies ist dasselbe wie Parallel GC, außer dass es mehrere Threads für die Garbage Collection sowohl der jungen als auch der alten Generation verwendet.
  4. Concurrent Mark Sweep (CMS) Collector (-XX:+UseConcMarkSweepGC): Der CMS Collector wird auch als nebenläufiger Niedrigpausen-Collector bezeichnet. Er führt die Garbage Collection für die alte Generation durch. Der CMS Collector versucht, die Pausen aufgrund der Garbage Collection zu minimieren, indem er den Großteil der Garbage Collection-Arbeit nebenläufig mit den Anwendungsthreads ausführt. Der CMS Collector verwendet für die Garbage Collection der jungen Generation denselben Algorithmus wie der parallele Collector. Dieser Garbage Collector eignet sich für reaktionsfähige Anwendungen, bei denen längere Pausenzeiten nicht akzeptabel sind. Wir können die Anzahl der Threads im CMS Collector mithilfe der -XX:ParallelCMSThreads=n JVM-Option begrenzen.
  5. G1 Garbage Collector (-XX:+UseG1GC): Der Garbage First oder G1 Garbage Collector ist ab Java 7 verfügbar und sein langfristiges Ziel ist es, den CMS Collector zu ersetzen. Der G1 Collector ist ein paralleler, nebenläufiger und inkrementell kompakter Garbage Collector mit geringer Pause. Der Garbage First Collector funktioniert nicht wie andere Collectors, und es gibt kein Konzept von Young- und Old-Generation-Speicherplatz. Er teilt den Heap-Speicher in mehrere gleich große Heap-Regionen auf. Wenn eine Garbage Collection aufgerufen wird, sammelt er zunächst die Region mit weniger lebenden Daten, daher „Garbage First“. Weitere Details dazu finden Sie unter Garbage-First Collector Oracle Documentation.

Speicherverwaltung in Java – Überwachung der Java Garbage Collection

Wir können sowohl die Java-Befehlszeile als auch UI-Tools verwenden, um die Garbage-Collection-Aktivitäten einer Anwendung zu überwachen. Für mein Beispiel verwende ich eine der Demos, die von Java SE Downloads bereitgestellt werden. Wenn Sie dieselbe Anwendung verwenden möchten, gehen Sie zur Java SE Downloads-Seite und laden Sie JDK 7 und JavaFX Demos und Beispiele herunter. Die Beispielanwendung, die ich verwende, ist Java2Demo.jar und sie befindet sich im Verzeichnis jdk1.7.0_55/demo/jfc/Java2D. Dies ist jedoch ein optionaler Schritt, und Sie können die GC-Überwachungsbefehle für jede Java-Anwendung ausführen. Der von mir verwendete Befehl zum Starten der Demosanwendung lautet:

pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar

jstat

Wir können das Befehlszeilentool jstat verwenden, um den JVM-Speicher und die Garbage-Collection-Aktivitäten zu überwachen. Es wird mit dem Standard-JDK geliefert, sodass Sie nichts weiter tun müssen, um es zu erhalten. Um jstat auszuführen, müssen Sie die Prozess-ID der Anwendung kennen, die Sie ganz einfach mit dem Befehl ps -eaf | grep java erhalten können.

pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar
  501 9582  11579   0  9:48PM ttys000    0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar
  501 14073 14045   0  9:48PM ttys002    0:00.00 grep Java2Demo.jar

Die Prozess-ID für meine Java-Anwendung lautet also 9582. Jetzt können wir den Befehl jstat wie folgt ausführen.

pankaj@Pankaj:~$ jstat -gc 9582 1000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
1024.0 1024.0  0.0    0.0    8192.0   7933.3   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8026.5   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8030.0   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8122.2   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  0.0    0.0    8192.0   8171.2   42108.0    23401.3   20480.0 19990.9    157    0.274  40      1.381    1.654
1024.0 1024.0  48.7   0.0    8192.0   106.7    42108.0    23401.3   20480.0 19990.9    158    0.275  40      1.381    1.656
1024.0 1024.0  48.7   0.0    8192.0   145.8    42108.0    23401.3   20480.0 19990.9    158    0.275  40      1.381    1.656

Das letzte Argument für jstat ist das Zeitintervall zwischen jeder Ausgabe. Es werden also alle 1 Sekunde Speicher- und Garbage-Collection-Daten gedruckt. Lassen Sie uns nun jede der Spalten nacheinander durchgehen.

  • S0C und S1C: Diese Spalte zeigt die aktuelle Größe der Survivor0- und Survivor1-Bereiche in KB an.S0U und S1U: Diese Spalte zeigt die aktuelle Nutzung der Survivor0- und Survivor1-Bereiche in KB an. Beachten Sie, dass einer der Survivor-Bereiche die ganze Zeit leer ist.
  • S0U und S1U: Diese Spalte zeigt den aktuellen Verbrauch der Survivor0 und Survivor1 Bereiche in KB an. Beachten Sie, dass zu jedem Zeitpunkt einer der Survivor Bereiche leer ist.
  • EC und EU: Diese Spalten zeigen die aktuelle Größe und den Verbrauch des Eden-Speichers in KB an. Beachten Sie, dass die EU-Größe zunimmt und sobald sie die EC überschreitet, wird ein Minor GC aufgerufen und die EU-Größe wird reduziert.
  • OC und OU: Diese Spalten zeigen die aktuelle Größe und den aktuellen Verbrauch des Altersgenerationsspeichers in KB an.
  • PC und PU: Diese Spalten zeigen die aktuelle Größe und den aktuellen Verbrauch des Perm Gen in KB an.
  • YGC und YGCT: Die YGC-Spalte zeigt die Anzahl der in der jungen Generation aufgetretenen GC-Ereignisse an. Die YGCT-Spalte zeigt die kumulierte Zeit für GC-Operationen für die junge Generation an. Beachten Sie, dass beide in der gleichen Zeile zunehmen, in der der EU-Wert aufgrund eines Minor GC abfällt.
  • FGC und FGCT: Die FGC-Spalte zeigt die Anzahl der aufgetretenen Full GC-Ereignisse an. Die FGCT-Spalte zeigt die kumulierte Zeit für Full GC-Operationen an. Beachten Sie, dass die Full GC-Zeit im Vergleich zu den Zeiten der jungen Generation GC sehr hoch ist.
  • GCT: Diese Spalte zeigt die gesamtkumulierte Zeit für GC-Operationen an. Beachten Sie, dass es die Summe der YGCT- und FGCT-Spaltenwerte ist.

Der Vorteil von jstat ist, dass es auch auf Remote-Servern ausgeführt werden kann, bei denen wir kein GUI haben. Beachten Sie, dass die Summe von S0C, S1C und EC10m beträgt, wie durch die JVM-Option -Xmn10m angegeben.

Java VisualVM mit Visual GC

Wenn Sie Speicher- und GC-Operationen im GUI sehen möchten, können Sie das Tool jvisualvm verwenden. Java VisualVM ist auch Teil des JDK, sodass Sie es nicht separat herunterladen müssen. Führen Sie einfach den Befehl jvisualvm im Terminal aus, um die Java VisualVM-Anwendung zu starten. Nach dem Start müssen Sie das Visual GC-Plugin auswählen und installieren, wie im folgenden Bild gezeigt. Nach der Installation von Visual GC öffnen Sie einfach die Anwendung aus der linken Spaltenübersicht und wechseln zur Visual GC-Sektion. Dort erhalten Sie ein Bild des JVM-Speichers und der Details zur Garbage Collection, wie im folgenden Bild gezeigt.

Java Garbage Collection Tuning

Java Garbage Collection Tuning sollte die letzte Option sein, die Sie verwenden, um die Durchsatzleistung Ihrer Anwendung zu erhöhen, und nur dann, wenn Sie eine Leistungsabnahme aufgrund längerer GC-Zeiten feststellen, die zu einem Anwendungszeitüberschreitungsfehler führen. Wenn Sie java.lang.OutOfMemoryError: PermGen space-Fehlerprotokolle sehen, versuchen Sie, den PermGen-Speicherplatz zu überwachen und zu erhöhen, indem Sie die JVM-Optionen -XX:PermGen und -XX:MaxPermGen verwenden. Sie könnten auch versuchen, -XX:+CMSClassUnloadingEnabled zu verwenden und zu überprüfen, wie es mit dem CMS-Garbage-Collector funktioniert. Wenn Sie viele Full-GC-Vorgänge sehen, sollten Sie versuchen, den Speicherplatz für den Old-Generation-Bereich zu erhöhen. Insgesamt erfordert die Abstimmung der Garbage Collection viel Aufwand und Zeit, und es gibt keine feste Regel dafür. Sie müssten verschiedene Optionen ausprobieren und vergleichen, um die beste für Ihre Anwendung geeignete Option zu finden. Das ist alles zum Thema Java-Speichermodell, Speicherverwaltung in Java und Garbage Collection. Ich hoffe, es hilft Ihnen, den JVM-Speicher und den Garbage-Collection-Prozess zu verstehen.

Source:
https://www.digitalocean.com/community/tutorials/java-jvm-memory-model-memory-management-in-java