Einfaches Verständnis von JVM

Apropos JVM

1. Überblick über die JVM-Speicherstruktur

Fügen Sie hier eine Bildbeschreibung ein

Der Java-Quellcode wird in eine Java-Klassendatei kompiliert und über den Klassenlader ClassLoader in die JVM geladen

  • Klassen werden im Methodenbereich gespeichert
  • Von der Klasse erstellte Objekte werden im Heap gespeichert
  • Beim Aufrufen von Methoden für Objekte im Heap werden der Stapel der virtuellen Maschine, der Stapel lokaler Methoden und der Programmzähler verwendet
  • Jede Codezeile wird beim Ausführen der Methode vom Interpreter Zeile für Zeile ausgeführt
  • Hotcode wird vom JIT-Compiler just-in-time kompiliert
  • Der Garbage-Collection-Mechanismus stellt Ressourcen im Heap wieder her
  • Der Umgang mit dem Betriebssystem erfordert den Aufruf der nativen Methodenschnittstelle
  1. Programmzähler (Programmzählerregister)
    Jeder Thread verfügt über einen unabhängigen Programmzähler, der sehr wenig Platz einnimmt. Er zeichnet die Adresse des Bytecode-Befehls auf, den der Thread ausführt. Der Bytecode-Interpreter ändert seinen Wert, um den nächsten auszuführenden Befehl auszuwählen. Daher wird er als Indikator für den Programmkontrollfluss bezeichnet. Grundlegende Funktionen wie Verzweigung, Schleife, Springen, Ausnahmebehandlung und Thread-Wiederherstellung müssen alle auf diesen Zähler angewiesen sein, um abgeschlossen zu werden. Beachten Sie, dass der Zählerwert leer (undefiniert) sein sollte, wenn die lokale (native) Methode ausgeführt wird. Da die native Methode von C/C++ implementiert wird, wird sie nicht in Bytecode-Anweisungen kompiliert.

  2. Der Java Virtual Machine Stack (Java Virtual Machine Stack)
    ist ebenfalls privat für den Thread und hat denselben Lebenszyklus wie der Thread. Der Stack beschreibt das Speichermodell, wenn die Java-Methode ausgeführt wird. Die JVM erstellt auf dem Stapel einen Stapelrahmen, wenn jede Methode ausgeführt wird, um Informationen wie lokale Variablentabellen, Operandenstapel, dynamische Links und Methodenausgänge zu speichern. Der Prozess jedes Methodenaufrufs bis zum Abschluss der Ausführung entspricht dem Prozess eines Stapelrahmens vom Pushen bis zum Einfügen in den Stapel der virtuellen Maschine.

Die lokale Variablentabelle speichert verschiedene grundlegende Datentypen, die zur Kompilierungszeit bekannt sind (boolean, byte, char, short, int, float, long, double), Objektreferenzen und returnAddress-Typen (die auf die Adresse einer Bytecode-Anweisung zeigen).

Der Speicherplatz dieser Datentypen in der lokalen Variablentabelle wird durch einen lokalen Variablensteckplatz (Slot) dargestellt, in dem die 64-Bit-langen und doppelten Datentypen zwei Variablensteckplätze belegen, während die übrigen Datentypen nur einen belegen. Der von der lokalen Variablentabelle benötigte Speicherplatz wird während der Kompilierung zugewiesen. Bei der Eingabe einer Methode wird vollständig bestimmt, wie viel lokalen Variablenspeicher die Methode im Stapelrahmen zuweisen muss, und die Größe der lokalen Variablentabelle wird während der Ausführung der Methode nicht geändert.

Wenn die vom abnormalen Thread angeforderte Stapeltiefe größer ist als die von der virtuellen Maschine zulässige Tiefe, wird ein StackOverflowError ausgelöst. Der Stapel der virtuellen Maschine kann dynamisch erweitert werden. Wenn die Erweiterung nicht für genügend Speicher gilt, wird ein OutOfMemoryError ausgegeben (die Stapelkapazität der virtuellen HotSpot-Maschine kann nicht dynamisch erweitert werden).

  1. Der native Methodenstapel (Native Method Stacks)
    hat dieselbe Funktion wie der Java Virtual Machine Stack. Der Unterschied besteht darin, dass die native Methode darin gespeichert ist, dh die Methode, die in einer Nicht-Java-Sprache implementiert ist. Die native Methode ruft die native Bibliotheksschnittstelle (JNI) auf, sodass wir nur einen von native geänderten Methodennamen sehen können. Die native Methode realisiert Funktionen, die mit reinem Java nicht realisiert werden können, und hängt normalerweise mit dem Betriebssystem und der Hardware zusammen.

  2. Java-Heap (Java-Heap)
    Der Java-Heap ist der größte Teil des von JVM verwalteten Speichers. Es handelt sich um einen Bereich, der von allen Threads gemeinsam genutzt wird. Seine einzige Funktion besteht darin, Objektinstanzen zu speichern. Fast alle Objekte weisen hier Speicher zu. Deshalb. Der Java-Heap wird auch als GC-Heap bezeichnet, da der Garbage Collector Objektinstanzen zurückfordert, dh der Heap ist ein vom Garbage Collector verwalteter Bereich.

Im Java-Heap sind auch Regionen zugeordnet, beispielsweise die neue Generation, die alte Generation, die permanente Generation oder der Metaspace. In jeder Region werden Instanzen mit unterschiedlichen Überlebenszeiten gespeichert. Die meisten Instanzen werden in der neuen Generation erstellt. Untersuchungen zufolge haben die meisten Instanzen einen kurzen Lebenszyklus und können die neue Generation nicht überleben. Daher ist die neue Generation der Hauptwiederherstellungsbereich des Garbage Collectors.

Wenn im Heap kein Speicher zum Abschließen der Instanzzuweisung vorhanden ist und der Heap nicht mehr erweitert werden kann, wird ein OutOfMemoryError ausgelöst.

  1. Methodenbereich (Methodenbereich)
    Der Methodenbereich ist auch ein von Threads gemeinsam genutzter Bereich, der zum Speichern von Daten wie Typinformationen, Konstanten, statischen Variablen und vom Just-in-Time-Compiler kompilierten Code-Caches verwendet wird, die von der virtuellen Maschine geladen wurden. Obwohl die „Java Virtual Machine Specification“ den Methodenbereich als logischen Teil des Heaps beschreibt, gibt es einen Alias ​​namens „Non-Heap“, um ihn vom Java-Heap zu unterscheiden.

Wenn der Methodenbereich die Speicherzuweisungsanforderungen nicht erfüllen kann, wird ein OutOfMemoryError-Fehler ausgegeben.

  1. Der Laufzeitkonstantenpool (Runtime Constant Pool)
    ist Teil des Methodenbereichs. Zusätzlich zur Klassenversion, zu Feldern, Methoden, Schnittstellen und anderen Beschreibungsinformationen in der Klassendatei gibt es auch eine konstante Pooltabelle (Constant Pool Table), in der verschiedene während der Kompilierung generierte Literale und Symbolreferenzen gespeichert werden. Dieser Teil des Inhalts wird nach dem Laden der Klasse im Laufzeitkonstantenpool im Methodenbereich gespeichert.

2. Mechanismus der übergeordneten Delegation (Klassenladen)

Was ist der elterliche Delegationsmechanismus?

Wenn ein Klassenlader eine bestimmte .class-Datei laden muss, vertraut er diese Aufgabe zunächst seinem übergeordneten Klassenlader an und führt diesen Vorgang rekursiv aus. Wenn der übergeordnete Klassenlader sie nicht lädt, lädt er die Klasse selbst.

Warum den elterlichen Delegationsmechanismus verwenden?

Um ein wiederholtes Laden derselben .class-Datei zu verhindern, fragen Sie den Vorgesetzten durch Beauftragung, und es besteht keine Notwendigkeit, sie nach dem Laden zu laden.
Stellen Sie sicher, dass die .class-Kerndatei nicht manipuliert wird. Selbst wenn sie manipuliert wird, wird sie nicht geladen, und selbst wenn sie geladen wird, handelt es sich nicht um dasselbe Objekt, da dieselbe von verschiedenen Ladeprogrammen geladene .class-Datei nicht dasselbe Klassenobjekt ist, wodurch die Sicherheit der Klassenausführung gewährleistet wird

3. GC-Garbage-Collection-Mechanismus

1. Müllunterscheidungsmethode

Referenzzählung

  • Bestimmen Sie die Anzahl der Verweise auf ein Objekt, um festzustellen, ob das Objekt recycelt werden kann
  • Jede Objektinstanz verfügt über einen Referenzzähler, der bei Referenzierung +1 und bei Abschluss -1 beträgt.
  • Vorteile: hohe Ausführungseffizienz, geringe Auswirkungen auf die Programmausführung
  • Nachteile: Zirkelverweise können nicht erkannt werden, was zu Speicherverlusten führt

Algorithmus zur Erreichbarkeitsanalyse

Der von diesen Knoten ausgehende Pfad, der über eine Reihe von „GC Roots“-Objekten als Ausgangspunkt verläuft, wird als Referenzkette bezeichnet. Wenn für ein Objekt keine mit GC Roots verbundene Referenzkette vorhanden ist, bedeutet dies, dass das Objekt nicht verfügbar ist.

Objekte, die als GC Roots verwendet werden können:

  • Objekte, auf die im Stapel der virtuellen Maschine verwiesen wird (lokale Variablentabelle im Stapelrahmen)
  • Objekte, auf die durch statische Klasseneigenschaften im Methodenbereich verwiesen wird
  • Objekte, auf die durch Konstanten im Methodenbereich verwiesen wird
  • Das von JNI referenzierte Objekt (im Allgemeinen eine native Methode) im lokalen Methodenstapel

starke, weiche, schwache, Phantomreferenz

Vor JDK1.2 hatte ein Objekt nur zwei Zustände: „referenziert“ und „nicht referenziert“. Später erweiterte Java das Referenzkonzept und unterteilte Referenzen in vier Typen: Starke Referenz, Weiche Referenz, Schwache Referenz und Phantomreferenz. Die Stärke dieser vier Referenzen lässt allmählich nach.

  1. Starke Referenzen beziehen sich auf Referenzen, die häufig im Programmcode vorhanden sind, z. B. „Object obj=new Object()“. Der Garbage Collector wird die verbleibenden starken Referenzobjekte niemals recyceln.
  2. Soft-Referenzen: Die SoftReference-Klasse implementiert Soft-Referenzen. Bevor im System eine Speicherüberlaufausnahme auftritt, werden diese Objekte in den Recyclingbereich für das sekundäre Recycling aufgenommen.
  3. Schwache Referenzen: Die Klasse WeakReference implementiert schwache Referenzen. Objekte überleben nur bis zur nächsten Garbage Collection. Wenn der Garbage Collector funktioniert, werden Objekte, die nur mit schwachen Referenzen verknüpft sind, zurückgefordert, unabhängig davon, ob genügend Speicher vorhanden ist.
  4. Phantomreferenzen: Die PhantomReference-Klasse implementiert Phantomreferenzen. Eine Instanz eines Objekts kann nicht über eine virtuelle Referenz abgerufen werden. Der einzige Zweck des Festlegens einer virtuellen Referenzzuordnung für ein Objekt besteht darin, eine Systembenachrichtigung zu erhalten, wenn das Objekt vom Collector zurückgefordert wird.

2. Garbage-Collection-Algorithmus

Mark-and-Sweep

Der grundlegendste Erfassungsalgorithmus ist der „Mark-Sweep“-Algorithmus, der in zwei Phasen unterteilt ist: „Markieren“ und „Löschen“. Zunächst werden alle Objekte markiert, die zurückgefordert werden müssen, und alle markierten Objekte werden nach Abschluss der Markierung einheitlich zurückgefordert.

  • Vorteile: schnelle Verarbeitungsgeschwindigkeit
  • Nachteile: Dies führt zu diskontinuierlicher Speicherplatz- und Speicherfragmentierung (nach dem Löschen der Markierung wird eine große Anzahl diskontinuierlicher Speicherfragmente generiert. Eine zu starke Speicherplatzfragmentierung kann dazu führen, dass das Programm, wenn es in Zukunft größere Objekte zuweisen muss, nicht genügend kontinuierlichen Speicher findet und im Voraus eine weitere Speicherbereinigungsaktion auslösen muss.)

Markierungsmethode

  • Markieren Sie Objekte, auf die GC Root nicht verweist
  • Bereinigen Sie referenzierte Objekte
  • Vorteile: kontinuierlicher Speicherplatz, keine Speicherfragmentierung
  • Nachteile: Aufräumen führt zu geringerer Effizienz

Kopieralgorithmus

Teilen Sie den verfügbaren Speicher je nach Kapazität in zwei gleich große Teile auf und nutzen Sie jeweils nur einen davon. Wenn der Speicher dieses Blocks aufgebraucht ist, kopieren Sie das verbleibende Objekt in einen anderen Block und bereinigen Sie dann den verwendeten Speicherplatz auf einmal.

Auf diese Weise wird jedes Mal der gesamte halbe Bereich zurückgefordert, und bei der Speicherzuweisung müssen keine komplexen Situationen wie Speicherfragmentierung berücksichtigt werden. Sie müssen lediglich den Zeiger oben auf dem Heap bewegen und den Speicher der Reihe nach zuweisen, was einfach zu implementieren und effizient zu bedienen ist. Der Preis dieses Algorithmus besteht lediglich darin, den Speicher auf die Hälfte des Originals zu reduzieren

  • Weisen Sie gleich großen Speicherplatz zu
  • Markieren Sie Objekte, auf die von GC Root verwiesen wird
  • Kopieren Sie referenzierte Objekte kontinuierlich in neuen Speicherplatz
  • Löschen Sie den ursprünglichen Speicherplatz
  • Tauschen Sie FROM-Raum und TO-Raum aus
  • Vorteile: kontinuierlicher Speicherplatz, keine Speicherfragmentierung
  • Nachteil: Benötigt doppelt so viel Speicherplatz

Generationsübergreifender Garbage-Collection-Mechanismus

Teilen Sie mehrere Speicherbereiche nach überlebenden Objekten auf, im Allgemeinen in neue Generation und alte Generation unterteilt. Formulieren Sie dann entsprechende Recycling-Algorithmen entsprechend den Merkmalen jeder Epoche.

neue Generation

Bei jeder Speicherbereinigung sterben viele Objekte, und nur eine kleine Anzahl überlebt. Es ist sinnvoller, einen Replikationsalgorithmus zu wählen.

Alte Generation

Die Überlebensrate von Objekten der alten Generation ist hoch und es gibt keine zusätzliche Speicherplatzzuweisung, um dies zu gewährleisten. Sie müssen also zum Recyceln den 标记 —— 清除oder- 标记 —— 整理Algorithmus verwenden.

3. Speicherzuweisungs- und Wiederherstellungsstrategie

Objekte werden zuerst in Eden zugewiesen

Objekte werden hauptsächlich im Eden-Bereich der neuen Generation zugewiesen. Wenn der lokale Thread-Zuweisungspuffer aktiviert ist, wird der Thread zuerst auf (TLAB) zugewiesen. In einigen Fällen wird es direkt in der alten Generation zugewiesen.

GC der neuen Generation (Minor GC)

Garbage-Collection-Aktionen der neuen Generation erfolgen häufig und schnell.

GC der alten Generation (Major GC / Full GC)

Bei der Garbage-Collection-Aktion, die im Alter auftritt, gibt es einen Major GC, der oft von mindestens einem Minor GC (nicht absolut) begleitet wird. Major GC ist im Allgemeinen mehr als zehnmal langsamer als Minor GC.

  • Große Objekte gehen direkt an die alte Generation

  • Langlebige Objekte werden in die alte Generation übergehen

  • Dynamische Bestimmung des Objektalters

  • Platzzuteilungsgarantie

4. Native Schlüsselwörter

Alles mit dem nativen Schlüsselwort bedeutet, dass der Java-Bereich nicht erreicht werden kann und die Bibliothek der C-Sprache aufgerufen wird

Geben Sie den nativen Methodenstapel ein, rufen Sie die native Methodenschnittstelle JNI auf, die Funktion von JNI: Erweitern Sie die Verwendung von Java, integrieren Sie verschiedene Programmiersprachen für Java und öffnen Sie zunächst einen markierten Bereich im Speicherbereich, um C und C ++ zu integrieren: Native Methodenstapel, Registrierung nativer Methoden. Zum Zeitpunkt der endgültigen Ausführung lädt die Ausführungs-Engine die Methoden in die lokale Methodenbibliothek über JNI. Derzeit wird diese Methode immer weniger verwendet, mit Ausnahme von hardwarebezogenen Anwendungen, z. B. der Steuerung von Druckern durch Java-Programme, Java-Systemverwaltungsproduktionsgeräten usw ., die in Unternehmensanwendungen relativ selten
sind
.
. Da die Kommunikation zwischen heterogenen Feldern mittlerweile sehr weit entwickelt ist, kann beispielsweise die Socket-Kommunikation und auch der Webservice verwendet werden.

PC-Register

  • Der Speicherplatz ist so klein, dass er fast vernachlässigbar ist. Es ist auch der am schnellsten laufende Bereich
    . In der JVM-Spezifikation verfügt jeder Thread über ein eigenes Programmiergerät. Es ist für den Thread privat und sein Deklarationszeitraum stimmt mit dem Lebenszyklus des Threads überein.
  • Zu jedem Zeitpunkt wird in einem Thread eine Methode ausgeführt, die sogenannte aktuelle Methode. Der Programmzähler speichert die JVM-Anweisungsadresse der Java-Methode, die vom aktuellen Thread ausgeführt wird.

Methodenbereich

Gespeichert: Konstanten, statische Variablen, Klasseninformationen, Konstantenpool

Fünf, der Stapel

Programm = Datenstruktur + Algorithmus

Wer zuerst reinkommt, mahlt zuerst raus, der Letzte rein, mahlt zuerst raus. Stapelspeicher, die Ausführung des Supervisor-Programms, der Lebenszyklus wird mit dem Thread synchronisiert; wenn der Thread endet, wird der Stapelspeicher freigegeben. Für den Stapel gibt es kein Garbage-Collection-Problem , denn sobald der Thread endet, ist der Stapel tot.

Speicherung im Stapel : 8 Grundtypen + Objektreferenz + Instanzmethode

**Funktionsprinzip: **Stapelrahmen

参考:https://blog.csdn.net/ATFWUS/article/details/104536028?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162072933516780269899425%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162072933516780269899425&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-104536028.first_rank_v2_pc_rank_v29&utm_term=%E6%A0%88%E5%B8%A7

Der Stapel ist voll: StackOverFlowRrror

Die Beziehung zwischen Stapel + Heap + Methodenbereich

Die Java-Klasse instanziiert den In-Memory-Prozess

[Externer Link zur Bildübertragung ist fehlgeschlagen. Die Quellseite verfügt möglicherweise über einen Anti-Leeching-Mechanismus. Es wird empfohlen, das Bild zu speichern und direkt hochzuladen (img-5exZcBAg-1620835238845) (C:\Users\HP\Desktop\JVM.assets\image-20210511210254145.png)]

Sechs, Haufen

Heap, eine JVM verfügt nur über einen Heap-Speicher und die Größe des Heap-Speichers kann angepasst werden.

Nachdem der Klassenlader die Klassendatei gelesen hat, legt er im Allgemeinen die Klasse, Methode, Konstante und Variable auf dem Heap ab, um die realen Objekte aller unserer Referenztypen zu speichern.

Der Heap-Speicher lässt sich in drei Bereiche unterteilen:

  • Neugeborenenbereich: Er ist in drei Bereiche unterteilt: Eden Park, Survival Area 0 und Survival Area 1.

    ​ Er ist ein Ort, an dem eine Klasse geboren wird und wächst und sogar stirbt

    Eden Park: Alle Objekte sind hier neu

    Unter der Annahme, dass es im Eden Park 10 Plätze gibt, wird ein leichter CG (normaler GC) ausgelöst, wenn er voll ist. Einige Objekte überlebten, solange es noch Hinweise auf sie gab, und diejenigen, die starben, verschwanden. Diejenigen, die überleben, gehen in den Überlebensbereich. Wenn der Überlebensbereich ebenfalls voll ist und schwere GC (globale GC) auslöst, werden diejenigen, die überleben, in den Seniorenbereich gehen.

    Unter normalen Umständen sind 99 % der Objekte temporäre Objekte und nur wenige überleben bis ins hohe Alter

    Überlebensbereich (0, 1)

  • Seniorenbereich:

  • Permanente Zone:

    Dieser Bereich befindet sich im Speicher und wird zum Speichern der Klassenobjekte verwendet, die mit dem JDK geliefert werden. Es speichert einige Umgebungs- oder Klasseninformationen der Java-Laufzeit. In diesem Bereich gibt es keine Speicherbereinigung . Der Speicher wird freigegeben, wenn die virtuelle Maschine heruntergefahren wird.

    Vor jdk1.6: Permanente Generierung befindet sich der Konstantenpool im Methodenbereich

    jdk1.7: Die permanente Generation wird schrittweise degradiert. Es gibt ein Konzept namens „Löschen der permanenten Generation“. Der konstante Pool befindet sich im Heap

    Nach jdk1.8: Es gibt keine permanente Generation, der Konstantenpool befindet sich im Metaspace

    Unter welchen Umständen wird die Dauerzone zusammenbrechen?

    Eine Startup-Klasse lädt eine große Anzahl von JAR-Paketen von Drittanbietern und Tomcat stellt zu viele Anwendungen bereit oder generiert eine große Anzahl von Reflektionsklassen. Werden diese kontinuierlich geladen, bis der Speicher voll ist, erscheint OOM.

Die GC-Müllsammlung erfolgt hauptsächlich im neuen Bereich und im alten Bereich

Angenommen, der Speicher ist voll, OOM, und der Heap-Speicher reicht nicht aus.

Nach JDK8 wurde der permanente Bereich in Metaspace umbenannt

Optimierung des Heap-Speichers

1. Versuchen Sie, den Heap-Speicher zu erweitern

-Xms: Legen Sie die anfängliche Heap-Speicherzuteilungsgröße fest, der Standardwert ist 1/64 des physischen Speichers.
-Xmx: Legen Sie die maximale Speicherzuteilungsgröße fest, der Standardwert ist 1/4 des physischen Speichers
-XX:+PrintGCDetails. : Geben Sie das angenommene GC-Verarbeitungsprotokoll aus

-Xms1024m -Xmx1024m -XX;+PrintGCDetails

Wenn im Projekt plötzlich ein OOM-Fehler auftritt, wie sollte dieser behoben werden?

  1. Verwenden Sie das Speicher-Snapshot-Analysetool Jprofiler
  2. Debuggen, zeilenweise Analyse

JProfiler-Rolle:

  • Analysieren Sie Dump-Speicherdateien, um Speicherlecks schnell zu lokalisieren
  • Holen Sie sich die Daten im Heap
  • Holen Sie sich ein großes Objekt

Acho que você gosta

Origin blog.csdn.net/python_mopagunda/article/details/116724771
Recomendado
Clasificación