Hallo allerseits! Wir möchten die Übersetzung des heutigen Materials mit dem Start eines neuen Threads im
Java Developer- Kurs zusammenfallen lassen, der morgen beginnt. Nun, fangen wir an.
JVM kann ein komplexes Tier sein. Glücklicherweise verbirgt sich der größte Teil dieser Komplexität unter der Haube, und wir als Anwendungsentwickler und Verantwortlicher für die Bereitstellung müssen uns häufig keine großen Sorgen machen. Aufgrund der wachsenden Beliebtheit der Technologie für die Bereitstellung von Anwendungen in Containern lohnt es sich jedoch, auf die Speicherzuweisung in der JVM zu achten.
Zwei Arten von ErinnerungenDie JVM unterteilt den Speicher in zwei Hauptkategorien: Heap und Nicht-Heap. Ein Heap ist ein Teil des JVM-Speichers, mit dem Entwickler am besten vertraut sind. Hier werden die von der Anwendung erstellten Objekte gespeichert. Sie bleiben dort, bis sie vom Müllsammler entfernt werden. In der Regel hängt die von der Anwendung verwendete Heap-Größe von der aktuellen Last ab.
Der Speicher außerhalb des Heapspeichers ist in mehrere Bereiche unterteilt. In HotSpot können Sie den
NMT- Mechanismus
(Native Memory Tracking) verwenden, um die Bereiche dieses Speichers zu erkunden. Beachten Sie, dass NMT zwar nicht die Verwendung des gesamten nativen Speichers verfolgt (
z. B. nicht die Zuweisung des nativen Speichers durch Code von Drittanbietern ), seine Funktionen jedoch für die meisten typischen Spring-Anwendungen ausreichen. Um NMT zu verwenden, führen Sie die Anwendung mit der
-XX:NativeMemoryTracking=summary
and using
jcmd VM.native_memory Zusammenfassung siehe Informationen zum verwendeten Speicher.
Schauen wir uns NMT als Beispiel unserer alten Freundin Petclinic an . Das folgende Diagramm zeigt die Verwendung des JVM-Speichers gemäß NMT-Daten (abzüglich des eigenen NMT-Overheads) beim Starten von Petclinic mit einer maximalen Heap-Größe von 48 MB (
-Xmx48M
):

Wie Sie sehen können, macht der Speicher außerhalb des Heaps den größten Teil des verwendeten JVM-Speichers aus, und der Heap-Speicher macht nur ein Sechstel der Gesamtmenge aus. In diesem Fall sind es ungefähr 44 MB (von denen 33 MB unmittelbar nach der Speicherbereinigung verwendet wurden). Die Auslastung des Heapspeichers betrug 223 MB.
Bereiche des nativen GedächtnissesKomprimierter Klassenraum : Dient zum Speichern von Informationen zu geladenen Klassen. Beschränkt auf den Parameter
MaxMetaspaceSize
. Eine Funktion der Anzahl der geladenen Klassen.
Anmerkung des ÜbersetzersAus irgendeinem Grund schreibt der Autor über den komprimierten Klassenraum und nicht über den gesamten Klassenbereich. Der komprimierte Klassenbereich ist Teil des Klassenbereichs, und der Parameter MaxMetaspaceSize
begrenzt die Größe des gesamten Klassenbereichs, nicht nur des komprimierten Klassenbereichs. Um den "komprimierten Klassenraum" einzuschränken, wird der Parameter CompressedClassSpaceSize
verwendet.
Von hier aus :
Wenn UseCompressedOops
ist und UseCompressedClassesPointers
verwendet wird, werden zwei logisch unterschiedliche Bereiche des nativen Speichers für Klassenmetadaten verwendet ...
Für diese komprimierten Klassenzeiger (die 32-Bit-Offsets) wird eine Region zugewiesen. Die Größe der Region kann mit CompressedClassSpaceSize
und beträgt standardmäßig 1 Gigabyte (GB) ...
Die MaxMetaspaceSize
gilt für die Summe des festgeschriebenen komprimierten Klassenraums und des Raums für die anderen Klassenmetadaten
Wenn der Parameter UseCompressedOops
und UseCompressedOops
verwendet wird, werden zwei logisch unterschiedliche Bereiche des nativen Speichers für Klassenmetadaten verwendet ...
Für komprimierte Zeiger wird ein Speicherbereich zugewiesen (32-Bit-Offsets). Die Größe dieses Bereichs kann mit CompressedClassSpaceSize
und beträgt standardmäßig 1 GB ...
Der Parameter MaxMetaspaceSize
bezieht sich auf die Summe des komprimierten Zeigerbereichs und des Bereichs für andere Klassenmetadaten.
- Thread: Der von Threads in der JVM verwendete Speicher. Die Funktion der Anzahl der laufenden Threads.
- Code-Cache: Der von JIT zum Ausführen verwendete Speicher. Eine Funktion der Anzahl der geladenen Klassen. Beschränkt auf
ReservedCodeCacheSize
. Sie können die Einstellung von JIT reduzieren, indem Sie beispielsweise die gestufte Kompilierung deaktivieren. - GC (Garbage Collector): Speichert die vom Garbage Collector verwendeten Daten. Hängt vom verwendeten Garbage Collector ab.
- Symbol: Speichert Zeichen wie Feldnamen, Methodensignaturen und internierte Zeichenfolgen. Eine übermäßige Verwendung des Zeichenspeichers kann darauf hinweisen, dass die Zeilen zu intern sind.
- Intern: Speichert andere interne Daten, die in keinem der anderen Bereiche enthalten sind.
UnterschiedeIm Vergleich zu einem Heap ändert sich der Off-Heap-Speicher unter Last weniger. Sobald die Anwendung alle Klassen lädt, die verwendet werden, und die JIT vollständig aufgewärmt ist, wird alles in einen stabilen Zustand versetzt. Um eine Verringerung der Verwendung des
komprimierten Klassenbereichs festzustellen , muss der Klassenladeprogramm, das die Klassen geladen hat, vom Garbage Collector entfernt werden. Dies war in der Vergangenheit üblich, als Anwendungen in Servlet-Containern oder Anwendungsservern bereitgestellt wurden (der Anwendungsklassenlader wurde vom Garbage Collector entfernt, als die Anwendung vom Anwendungsserver entfernt wurde), dies ist jedoch bei modernen Ansätzen zur Anwendungsbereitstellung selten der Fall.
Konfigurieren Sie JVMDie Konfiguration der JVM zur effizienten Nutzung des verfügbaren Arbeitsspeichers ist nicht einfach. Wenn Sie die JVM mit dem Parameter
-Xmx16M
und erwarten, dass nicht mehr als 16 MB Arbeitsspeicher verwendet werden, erhalten Sie eine unangenehme Überraschung.
Ein interessanter Bereich des JVM-Speichers ist der JIT-Code-Cache. Standardmäßig verwendet HotSpot JVM bis zu 240 MB. Wenn der Code-Cache zu klein ist, verfügt die JIT möglicherweise nicht über genügend Speicherplatz zum Speichern ihrer Daten. Infolgedessen wird die Leistung verringert. Wenn der Cache zu groß ist, wird möglicherweise der Speicher verschwendet. Bei der Bestimmung der Größe eines Caches ist es wichtig, die Auswirkungen auf die Speichernutzung und die Leistung zu berücksichtigen.
Wenn Sie in einem Docker-Container ausgeführt werden, kennen die neuesten Versionen von Java
jetzt die Speicherbeschränkungen des Containers und versuchen, die Größe der JVM entsprechend zu ändern. Leider wird häufig viel Speicher außerhalb des Heaps zugewiesen und nicht genug im Heap. Angenommen, Sie haben eine Anwendung in einem Container mit 2 Prozessoren und 512 MB verfügbarem Speicher. Sie möchten mehr Workload bewältigen und die Anzahl der Prozessoren auf 4 und den Speicher auf 1 GB erhöhen. Wie oben erläutert, variiert die Größe des Heapspeichers normalerweise mit der Last, und der Speicher außerhalb des Heapspeichers ändert sich erheblich weniger. Daher erwarten wir, dass der größte Teil der zusätzlichen 512 MB dem Heap zugewiesen wird, um die erhöhte Last zu bewältigen. Leider wird die JVM dies standardmäßig nicht tun und zusätzlichen Speicher mehr oder weniger gleichmäßig zwischen dem Speicher auf dem Heap und außerhalb des Heaps verteilen.
Glücklicherweise verfügt das CloudFoundry-Team über umfassende Kenntnisse zur Speicherzuweisung in der JVM. Wenn Sie Anwendungen auf CloudFoundry herunterladen, wendet das Build Pack dieses Wissen automatisch auf Sie an. Wenn Sie CloudFoudry nicht verwenden oder mehr über die Konfiguration von JVM erfahren möchten, wird empfohlen, die
Beschreibung der dritten Version des
Speicherrechners von
Java Buildpack zu lesen.
Was bedeutet das für den Frühling?Das Spring-Team verbringt viel Zeit damit, über Leistung und Speichernutzung nachzudenken, und erwägt die Möglichkeit, Speicher sowohl auf dem Heap als auch außerhalb des Heaps zu verwenden. Eine Möglichkeit, die Speichernutzung außerhalb des Heapspeichers zu begrenzen, besteht darin, Teile des Frameworks so vielseitig wie möglich zu gestalten. Ein Beispiel hierfür ist die Verwendung von Reflection, um Abhängigkeiten zu erstellen und in die Beans Ihrer Anwendung einzufügen. Durch die Verwendung von Reflection bleibt die Menge des von Ihnen verwendeten Framework-Codes unabhängig von der Anzahl der Beans in Ihrer Anwendung konstant. Um die Startzeit zu optimieren, verwenden wir den Cache auf dem Heap und löschen diesen Cache nach Abschluss des Starts. Der Heap-Speicher kann vom Garbage Collector problemlos bereinigt werden, um Ihrer Anwendung mehr verfügbaren Speicher zur Verfügung zu stellen.
Traditionell warten wir auf Ihre Kommentare zum Material.