Technologieaustausch

Populärwissenschaftlicher Artikel: Verstehen Sie den tatsächlichen Kampf von JVM in einem Artikel (4) Vertiefendes Verständnis der Fluchtanalyse Fluchtanalyse

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Überblick

Werden Objekte in Java im Heapspeicher zugewiesen?

Okay, es ist zu abstrakt. Mal sehen, wo der Speicher des folgenden Objekts zugewiesen ist.

  1. public void test() {
  2. Object object = new Object();
  3. }
  4. 这个方法中的object对象,是在堆中分配内存么?

Sagen Sie das Ergebnis: Das Objekt kann Speicher auf dem Stapel oder auf dem Heap zuweisen.

Hier ist der entscheidende Punkt: Um die Leistung der JVM zu verbessern und Speicherplatz zu sparen, bietet die JVM eine Funktion namens „Escape-Analyse“, eine derzeit relativ hochmoderne Optimierungstechnologie Java Virtual Machine ist auch eine sehr wichtige Optimierungstechnik für JIT. jdk6 hat erst mit der Einführung dieser Technologie begonnen, jdk7 hat begonnen, die Escape-Analyse standardmäßig zu aktivieren, und jdk8 hat begonnen, die Escape-Analyse zu verbessern, und hat sie standardmäßig aktiviert. Bis JDK 9 wird die Escape-Analyse als Standardoptimierungsmethode verwendet und es gibt keine speziellen Kompilierungsparameter sind erforderlich.

Verstehen Sie nun den Satz „Objekt kann Speicher auf dem Stapel zuweisen oder Speicher auf dem Heap zuweisen“. In JDK7 und 8 ist es möglich, Speicher auf dem Stapel zuzuweisen, da JDK7 Es wurde begonnen, nur die Escape-Analyse zu unterstützen; jdk9 wird höchstwahrscheinlich auf dem Stapel zugewiesen (das Objekt hier ist sehr klein), da jdk9 standardmäßig nur die Escape-Analyse wirklich unterstützt und aktiviert.

Mit der Entwicklung von JIT-Compilern (Just-in-Time-Compilern) und der allmählichen Reife der Escape-Analysetechnologie werden die Stapelzuweisungs- und Skalarersatzoptimierungstechnologie dazu führen, dass „alle Objekte auf dem Heap zugewiesen werden“ in Java weniger absolut werden In der virtuellen Maschine wird den Objekten Speicher im Heap zugewiesen. Es gibt jedoch einen Sonderfall: Wenn nach der Escape-Analyse festgestellt wird, dass ein Objekt der Methode nicht entkommt, kann es für die Zuweisung auf dem Stapel optimiert werden. Wenn die Methode ausgeführt wird, wird der Stapelrahmen geöffnet und das Objekt freigegeben. Dadurch entfällt die Notwendigkeit, Speicher auf dem Heap zuzuweisen und einer Speicherbereinigung zu unterziehen ().Hotspot macht dies derzeit nicht). Dies ist auch die am weitesten verbreitete Off-Heap-Speichertechnologie.

Nach JDK 6u23 (einprägsame Hauptversion JDK7) ist die Escape-Analyse in Hotspot standardmäßig aktiviert. Wenn Sie eine frühere Version verwenden, können Sie die Escape-Analyse über die Option „-XX:+DoEscapeAnalysis“ anzeigen. Filterergebnisse für die Escape-Analyse anzeigen.

Hotspot implementiert die Skalarersetzung durch Escape-Analyse (nicht maskierte Objekte werden durch Skalare und Aggregate ersetzt, was die Codeeffizienz verbessern kann), aber nicht maskierte Objekte weisen weiterhin Speicher auf dem Heap zu, sodass immer noch gesagt werden kann, dass alle Objekte zugewiesen sind Speicher auf dem Heap.

Darüber hinaus umfassend angepasst, basierend auf Open JDKTaoBao VMUnter anderem implementiert die innovative GCIH-Technologie (GC Invisible Heap) Off-Heap und verschiebt Objekte mit langen Lebenszyklen vom Heap nach außerhalb des Heaps. GC verwaltet keine Java-Objekte innerhalb von GCIH, wodurch die Häufigkeit des GC-Recyclings verringert und der Zweck verbessert wird der GC-Rückgewinnungseffizienz.

Stapel: Wenn jede Methode ausgeführt wird, wird gleichzeitig ein Stapelrahmen erstellt, um Informationen wie lokale Variablentabellen, Operationsstapel, dynamische Verbindungen, Methodenausgänge usw. zu speichern. Der Prozess vom Aufruf jeder Methode bis zum Abschluss der Ausführung entspricht dem Prozess eines Stapelrahmens vom Schieben in den Stapel bis zum Herausspringen aus dem Stapel im Stapel der virtuellen Maschine.

Haufen:Wenn ein Objekt instanziiert wird, wird das Objekt auf dem Heap zugewiesen und ein Verweis auf den Heap wird auf den Stapel verschoben.

Flucht:Wenn mehrere Methoden oder Threads auf einen Zeiger auf ein Objekt verweisen, sagen wir, dass der Zeiger maskiert wird. Im Allgemeinen werden Rückgabeobjekte und globale Variablen maskiert.

Fluchtanalyse:Die Methode zur Analyse dieses Escape-Phänomens wird Escape-Analyse genannt

Optimierung der Escape-Analyse – Zuordnung auf dem Stapel:Die Zuweisung auf dem Stapel bedeutet, dass die von der lokalen Variablen in der Methode generierte Instanz (es erfolgt kein Escape) auf dem Stapel zugewiesen wird und nicht im Heap zugewiesen werden muss. Nach Abschluss der Zuweisung wird die Ausführung im Aufrufstapel fortgesetzt. Schließlich endet der Thread, der Stapelraum wird recycelt und die lokalen Variablenobjekte werden ebenfalls recycelt.

Speicherzuweisungsprozess für Java-Objekte

  1. Wenn die Zuordnung auf dem Stapel (Escape-Analyse) aktiviert ist, führt die JVM zuerst die Zuordnung auf dem Stapel durch.
  2. Wenn die Zuweisung auf dem Stapel nicht aktiviert ist oder die Bedingungen nicht erfüllt sind, wird die TLAB-Zuweisung eingegeben.
  3. Wenn die TLAB-Zuweisung fehlschlägt oder inkonsistent ist, ermitteln Sie, ob die Zuweisung der alten Generation eingegeben werden kann.
  4. Wenn es nicht in die alte Generation eintreten kann, wird es in die Eden-Zuordnung aufgenommen.
  5. Nicht alle Objekte werden auf dem Heap zugewiesen. Zusätzlich zum Heap können Objekte auch auf dem Stapel und TLAB zugewiesen werden. (Die meisten Objekte werden im Heap zugewiesen)

Werden Objekte in Java unbedingt auf dem Heap zugewiesen?

Antwort: Nicht unbedingt.

Wenn die Bedingungen der Escape-Analyse erfüllt sind, kann ein Objekt auf dem Stapel zugewiesen werden.Reduzieren Sie die Heap-Speicherzuweisung und den GC-Druck.Da der Stapelspeicher begrenzt ist, wenn das Objekt die Bedingungen für die Skalarersetzung erfüllt,Eine weitere Operation wird am Objekt durchgeführt, um es in Teile zu zerlegen.Die spezifische Methode der Skalarersetzung ist: Die JVM zerlegt das Objekt weiter und zerlegt es in mehrere Mitgliedsvariablen, die von dieser Methode verwendet werden.Damit wird das Ziel einer besseren Ausnutzung des Stapelspeichers und der Register erreicht.

Wie Objekte auf dem Heap dem Stapel zugewiesen werden, erfordert eine Escape-Analyse.

Dies ist ein funktionsübergreifender globaler Datenflussanalysealgorithmus, der die Synchronisierungslast und den Speicher-Heap-Zuweisungsdruck in Java-Programmen effektiv reduzieren kann. Durch die Escape-Analyse kann der Java Hotspot-Compiler den Nutzungsbereich der Referenz eines neuen Objekts analysieren und entscheiden, ob dieses Objekt dem Heap zugewiesen werden soll.

Das grundlegende Verhalten der Escape-Analyse besteht darin, den dynamischen Umfang von Objekten zu analysieren:

  1. Wenn ein Objekt in einer Methode definiert ist und das Objekt nur innerhalb der Methode verwendet wird, wird davon ausgegangen, dass kein Escape aufgetreten ist.
  2. Wenn ein Objekt in einer Methode definiert ist und von einer externen Methode referenziert wird, erfolgt ein Escape. Beispielsweise als Aufrufparameter an andere Stellen übergeben.

Im Compiler-Optimierungsprinzip der Computersprache bezieht sich die Escape-Analyse auf die Methode zur Analyse des dynamischen Bereichs von Zeigern. Sie hängt mit der Zeigeranalyse und der Formanalyse des Compiler-Optimierungsprinzips zusammen. Wenn eine Variable (oder ein Objekt) in einer Methode zugewiesen wird, kann ihr Zeiger global zurückgegeben oder referenziert werden, worauf andere Methoden oder Threads verweisen. Dieses Phänomen wird als Zeiger- (oder Referenz-)Escape bezeichnet. Laienhaft ausgedrückt: Wenn der Zeiger eines Objekts von mehreren Methoden oder Threads referenziert wird, nennen wir den Zeiger (oder das Objekt) des Objekts Escape (da das Objekt zu diesem Zeitpunkt den lokalen Bereich der Methode oder des Threads verlässt).

Was ist Fluchtanalyse?

Kurzbeschreibung: „Escape-Analyse: Eine statische Analyse, die den dynamischen Bereich von Zeigern bestimmt. Sie kann analysieren, wo im Programm auf den Zeiger zugegriffen werden kann. Im Kontext der Just-in-Time-Kompilierung ermittelt die Escape-Analyse, ob der neu erstellte Objekt-Escapes.

Die Grundlage für die Just-in-Time-Kompilierung, um zu bestimmen, ob ein Objekt entkommt: Zum einen, ob das Objekt im Heap gespeichert ist (statisches Feld oder Instanzfeld des Objekts im Heap), und zum anderen, ob das Objekt übergeben wird unbekannter Code.

Escape Analysis ist derzeit eine relativ hochmoderne Optimierungstechnologie in virtuellen Java-Maschinen. Wie die Analyse von Typvererbungsbeziehungen handelt es sich nicht um ein Mittel zur direkten Optimierung von Code, sondern um eine Analysetechnologie, die eine Grundlage für andere Optimierungsmittel bietet.

Escape-Analyse: Hierbei handelt es sich um eine sehr wichtige JIT-Optimierungstechnologie, mit der bestimmt wird, ob auf das Objekt außerhalb der Methode zugegriffen wird, dh um den Bereich der Methode zu verlassen. Die Escape-Analyse ist ein Schritt des JIT-Compilers. Durch JIT können wir bestimmen, welche Objekte auf die Verwendung innerhalb der Methode beschränkt werden können und nicht nach außen entkommen. Anschließend können sie optimiert werden, indem sie beispielsweise auf dem Stapel anstelle des Heaps zugewiesen werden Oder führen Sie eine Skalarersetzung durch, um ein Objekt zur Speicherung in mehrere Grundtypen aufzuteilen. Es handelt sich um einen funktionsübergreifenden globalen Datenflussanalysealgorithmus, der die Synchronisierungslast, die Speicher-Heap-Zuweisung und den Speicherbereinigungsdruck in Java-Programmen effektiv reduzieren kann. Durch die Escape-Analyse kann der Java Hotspot-Compiler den Nutzungsbereich der Referenz eines neuen Objekts analysieren und entscheiden, ob dieses Objekt dem Heap zugewiesen werden soll.

Die Escape-Analyse konzentriert sich hauptsächlich auf lokale Variablen, um festzustellen, ob auf dem Heap zugewiesene Objekte den Gültigkeitsbereich der Methode verlassen haben. Es ist mit der Zeigeranalyse und Formanalyse von Compiler-Optimierungsprinzipien verbunden. Wenn eine Variable (oder ein Objekt) in einer Methode zugewiesen wird, kann ihr Zeiger global zurückgegeben oder referenziert werden, worauf andere Methoden oder Threads verweisen. Dieses Phänomen wird als Zeiger- (oder Referenz-)Escape bezeichnet. Laienhaft ausgedrückt: Wenn der Zeiger eines Objekts von mehreren Methoden oder Threads referenziert wird, dann sagen wir, dass der Zeiger des Objekts entkommen ist. Durch die richtige Gestaltung der Codestruktur und Datennutzung kann die Escape-Analyse besser genutzt werden, um die Programmleistung zu optimieren. Durch die Escape-Analyse können wir auch den Aufwand für die Zuweisung von Objekten auf dem Heap reduzieren und die Speichernutzung verbessern.

Bei der Escape-Analyse handelt es sich um eine Technik, mit der festgestellt wird, ob ein Objekt während seiner Lebensdauer aus dem Gültigkeitsbereich einer Methode entwichen ist. In der Java-Entwicklung wird die Escape-Analyse verwendet, um den Lebenszyklus und den Umfang von Objekten zu bestimmen, um entsprechende Optimierungen durchzuführen und die Programmleistung und Speichernutzungseffizienz zu verbessern.

Wenn ein Objekt erstellt wird, kann es innerhalb einer Methode verwendet werden oder an andere Methoden oder Threads übergeben werden und außerhalb der Methode weiterhin existieren. Wenn das Objekt den Gültigkeitsbereich der Methode nicht verlässt, kann die JVM es auf dem Stapel statt auf dem Heap zuweisen und so den Overhead der Heap-Speicherzuweisung und Garbage Collection vermeiden.

  1. 关于逃逸分析的论文在1999年就已经发表,但直到Sun JDK 1.6才实现了逃逸分析,而且直到现在这项优化尚未足够成熟,仍有很大的改进余地。不成熟的原因主要是不能保证逃逸分析的性能收益必定高于它的消耗。如果要完全准确的判断一个对象是否会逃逸,需要进行数据流敏感的一系列复杂分析,从而确定程序各个分支执行时对此对象的影响。这是一个相对高耗时的过程,如果分析完后发现没有几个不逃逸的对象,那这些运行期耗用的时间就白白浪费了,所以目前虚拟机只能采用不那么准确,但时间压力相对较小的算法来完成逃逸分析。还有一点是,基于逃逸分析的一些优化手段,如上面提到的“栈上分配”,由于HotSpot虚拟机目前的实现方式导致栈上分配实现起来比较复杂,因此在HotSpot中暂时还没有做这项优化。
  2. 在测试结果中,实施逃逸分析后的程序在MicroBenchmarks中往往能运行出不错的成绩,但是在实际的应用程序,尤其是大型程序中反而发现实施逃逸分析可能出现效果不稳定的情况,或因分析过程耗时但却无法有效判别出非逃逸对象而导致性能(即使编译的收益)有所下降,所以在很长的一段时间里,即使是Server Compiler,也默认不开启逃逸分析(在JDK 1.6 Update 23的Server Compiler中才开始默认开启了逃逸分析),甚至在某些版本(如JDK 1.6 Update 18)中还曾经短暂的完全禁止了这项优化。
  3. 如果有需要,并且确认对程序运行有益,用户可以使用参数-XX:+DoEscapeAnalysis来手动开启逃逸分析,开启之后可以通过参数-XX:+PrintEscapeAnalysis来查看分析结果。有了逃逸分析支持之后,用户可以使用参数-XX:EliminateAllocations来开启标量替换,使用+XX:+EliminateLocks来开启同步消除,使用参数-XX:PrintEliminateAllocations查看标量的替换情况。
  4. 尽管目前逃逸分析的技术仍不是十分成熟,但是他却是即时编译器优化技术的一个重要的方向,在今后的虚拟机中,逃逸分析技术肯定会支撑起一系列使用有效的优化技术。

Grundprinzipien der Fluchtanalyse

Das Grundprinzip der JVM-Escape-Analyse besteht darin, die Escape-Situation des Objekts durch zwei Analysemethoden zu bestimmen: statisch und dynamisch.

Im Java-Kompilierungssystem erfordert der Prozess der Umwandlung einer Java-Quellcodedatei in eine computerausführbare Maschinenanweisung zwei Kompilierungsstufen:

Der erste Abschnitt der Kompilierung bezieht sich auf den Front-End-Compiler.java-Dateikonvertiert zu.class-Datei (Bytecode-Datei). Front-End-Compilerprodukte können Javac von JDK oder der inkrementelle Compiler in Eclipse JDT sein.

In der zweiten Kompilierungsstufe interpretiert die JVM den Bytecode und übersetzt ihn in entsprechende Maschinenanweisungen, liest den Bytecode einzeln ein und interpretiert und übersetzt ihn einzeln in Maschinencode.

Offensichtlich ist seine Ausführungsgeschwindigkeit aufgrund des Zwischenprozesses der Interpretation zwangsläufig viel langsamer als die eines ausführbaren binären Bytecode-Programms. Dies ist die Funktion des traditionellen JVM-Interpreters (Interpreter).

Wie kann man Zwischenhändler ausschließen und die Effizienz steigern?

Um dieses Effizienzproblem zu lösen, wurde die JIT-Technologie (Just In Time Compiler) eingeführt.

Nach der Einführung der JIT-Technologie werden Java-Programme weiterhin über den Interpreter interpretiert und ausgeführt. Das heißt, der Hauptteil wird weiterhin interpretiert und ausgeführt, die Zwischenverbindungen werden jedoch teilweise entfernt.

JIT-Compiler (Just-in-timeCompiler) Just-in-Time-Kompilierung. Die früheste Java-Implementierungslösung bestand aus einer Reihe von Übersetzern (Interpretern), die jede Java-Anweisung in eine entsprechende Mikroprozessoranweisung übersetzten und diese nacheinander entsprechend der Reihenfolge der übersetzten Anweisungen ausführten, da eine Java-Anweisung in ein Dutzend oder Dutzende übersetzt werden konnte Bei äquivalenten Mikroprozessorbefehlen wird dieser Modus sehr langsam ausgeführt.

Wie entferne ich die Zwischenlinks teilweise?

Wenn die JVM feststellt, dass eine bestimmte Methode oder ein bestimmter Codeblock besonders häufig ausgeführt wird, betrachtet sie diese als „Hot Spot Code“. Dann übersetzt JIT einen Teil des „Hot Codes“ in Maschinencode, der sich auf die lokale Maschine bezieht, optimiert ihn und speichert dann den übersetzten Maschinencode für die nächste Verwendung zwischen.

Wo soll der übersetzte Maschinencode zwischengespeichert werden? Dieser Cache wird Code-Cache genannt. Es ist ersichtlich, dass die Methoden zum Erreichen einer hohen Parallelität zwischen JVM- und WEB-Anwendungen ähnlich sind und immer noch die Cache-Architektur verwenden.

Wenn die JVM das nächste Mal auf denselben heißen Code stößt, überspringt sie die Zwischenverbindung der Interpretation, lädt den Maschinencode direkt aus dem Code-Cache und führt ihn direkt aus, ohne ihn erneut zu kompilieren.

Daher besteht das übergeordnete Ziel von JIT darin, Hot-Code zu entdecken, und Hot-Code ist zum Schlüssel zur Verbesserung der Leistung geworden. So entstand der Name Hotspot-JVM. Es ist ein lebenslanges Streben, Hot-Code zu identifizieren und ihn in den Namen zu schreiben.

Daher lautet die Gesamtstrategie von JVM:

  • Für die meisten ungewöhnlichen Codes müssen wir keine Zeit damit verbringen, sie in Maschinencode zu kompilieren, sondern müssen sie interpretieren und ausführen.

  • Andererseits können wir heißen Code, der nur einen kleinen Teil einnimmt, in Maschinencode kompilieren, um die ideale Laufgeschwindigkeit zu erreichen.

Die Entstehung von JIT (Just-in-Time-Compilation) und der Unterschied zwischen Dolmetschern

(1) Der Interpreter interpretiert den Bytecode in Maschinencode. Auch wenn er beim nächsten Mal auf denselben Bytecode stößt, führt er dennoch eine wiederholte Interpretation durch.

(2) JIT kompiliert einige Bytecodes in Maschinencodes und speichert sie im Code-Cache. Wenn Sie das nächste Mal auf denselben Code stoßen, wird er direkt ohne erneute Kompilierung ausgeführt.

(3) Der Interpreter interpretiert Bytecode in Maschinencode, der allen Plattformen gemeinsam ist.

(4) JIT generiert plattformspezifischen Maschinencode basierend auf dem Plattformtyp.

JVM enthält mehrere Just-in-Time-Compiler, hauptsächlich C1 und C2 sowie Graal (experimentell).

Mehrere Just-in-Time-Compiler optimieren den Bytecode und generieren Maschinencode

  • C1 führt eine einfache und zuverlässige Optimierung des Bytecodes durch, einschließlich Methoden-Inlining, Devirtualisierung, Redundanzbeseitigung usw. Die Kompilierungsgeschwindigkeit ist schneller. Sie können die C1-Kompilierung über -client erzwingen.
  • C2 führt radikale Optimierungen am Bytecode durch, einschließlich Vorhersage der Verzweigungsfrequenz, synchrones Löschen usw. Die Angabe der C2-Kompilierung kann über -server erzwungen werden

JVM unterteilt den Ausführungsstatus in 5 Ebenen:

  • Level 0, Dolmetscher

  • Level 1, kompiliert und ausgeführt mit dem C1-Just-in-Time-Compiler (ohne Profilierung)

  • Schicht 2, kompiliert und ausgeführt mit dem C1-Just-in-Time-Compiler (mit grundlegender Profilerstellung)

  • Schicht 3, kompiliert und ausgeführt mit C1-Just-in-Time-Compiler (mit vollständiger Profilierung)

  • Level 4, kompiliert und ausgeführt mit dem C2-Just-in-Time-Compiler

Die JVM aktiviert C2 nicht direkt, sondern erfasst zunächst den Ausführungsstatus des Programms durch die C1-Kompilierung und bestimmt dann anhand der Analyseergebnisse, ob C2 aktiviert werden soll.

Im mehrschichtigen Kompilierungsmodus wird der Ausführungsstatus der virtuellen Maschine in fünf Schichten unterteilt, von einfach bis komplex, von schnell bis langsam.

Während der Kompilierung führt JIT nicht nur das Zwischenspeichern von Hotcode zur Beschleunigung des Prozesses durch, sondern führt auch zahlreiche Optimierungen am Code durch.

Der Zweck einiger Optimierungen besteht darin,Reduzieren Sie den Speicher-Heap-Zuweisungsdruck Eine der wichtigen Techniken bei der JIT-Optimierung wird als Escape-Analyse bezeichnet. Laut Escape-Analyse optimiert der Just-in-Time-Compiler den Code während des Kompilierungsprozesses wie folgt:

  • Sperrenbeseitigung: Wenn ein Sperrobjekt nur von einem Thread gesperrt ist, entfernt der Just-in-Time-Compiler die Sperre
  • Zuweisung auf dem Stapel: Wenn ein Objekt nicht entkommt, wird das Objekt direkt auf dem Stapel zugewiesen, da der Thread recycelt wird, da eine große Menge an JVM-Code die Heap-Zuweisung ist und die aktuelle JVM keine Zuweisung auf dem Stapel unterstützt. verwendet aber Skalarersatz.
  • Skalare Ersetzung: Wenn ein Objekt nicht entkommt, wird das aktuelle Objekt in mehrere lokale Variablen aufgeteilt und in der lokalen Variablentabelle des Stapels der virtuellen Maschine zugewiesen.

1. Statische Analyse ist eine Analyse, die zur Kompilierungszeit durchgeführt wird

Es überprüft die statische Struktur des Codes, um festzustellen, ob das Objekt entkommen kann. Wenn beispielsweise ein Objekt einer Mitgliedsvariablen einer Klasse zugewiesen oder an eine externe Methode zurückgegeben wird, kann festgestellt werden, dass das Objekt maskiert ist.

2. Dynamische Analyse ist eine zur Laufzeit durchgeführte Analyse

Es bestimmt, ob ein Objekt entkommt, indem es das Verhalten von Methodenaufrufen und Objektreferenzen beobachtet. Wenn beispielsweise mehrere Threads auf ein Objekt verweisen, kann davon ausgegangen werden, dass das Objekt entkommen ist.

Die Escape-Analyse führt eine eingehende Analyse des Codes durch, um festzustellen, ob das Objekt während der Lebensdauer der Methode außerhalb des Gültigkeitsbereichs der Methode entkommen ist. Wenn das Objekt nicht entkommt, kann die JVM es auf dem Stapel statt auf dem Heap zuordnen.

Escape-Status: globales Escape, Parameter-Escape, kein Escape

Ein Objekt hat drei Escape-Zustände: globales Escape, Parameter-Escape und kein Escape.

globale Flucht(GlobalEscape): Das heißt, der Bereich eines Objekts entweicht der aktuellen Methode oder dem aktuellen Thread.

Im Allgemeinen gibt es folgende Szenarien:
① Das Objekt ist eine statische Variable
② Das Objekt ist ein entkommenes Objekt
③ Das ​​Objekt wird als Rückgabewert der aktuellen Methode verwendet

Parameter-Escape(ArgEscape): Das heißt, ein Objekt wird als Methodenparameter übergeben oder durch einen Parameter referenziert, während des aufrufenden Prozesses erfolgt jedoch kein globales Escape. Dieser Status wird durch den Bytecode der aufgerufenen Methode bestimmt.

kein Entkommen: Das heißt, das Objekt in der Methode entkommt nicht.

Der Beispielcode für den Escape-Zustand lautet wie folgt:

  1. public class EscapeAnalysisTest {
  2. public static Object globalVariableObject;
  3. public Object instanceObject;
  4. public void globalVariableEscape(){
  5. globalVariableObject = new Object(); // 静态变量,外部线程可见,发生逃逸
  6. }
  7. public void instanceObjectEscape(){
  8. instanceObject = new Object(); // 赋值给堆中实例字段,外部线程可见,发生逃逸
  9. }
  10. public Object returnObjectEscape(){
  11. return new Object(); // 返回实例,外部线程可见,发生逃逸
  12. }
  13. public void noEscape(){
  14. Object noEscape = new Object(); // 仅创建线程可见,对象无逃逸
  15. }
  16. }

Fluchtwege: Methoden-Escape und Thread-Escape


1. Methoden-Escape: Definieren Sie in einem Methodenkörper eine lokale Variable, auf die von einer externen Methode verwiesen werden kann, z. B. indem sie als aufrufender Parameter an eine Methode übergeben oder direkt als Objekt zurückgegeben wird. Oder es kann verstanden werden, dass das Objekt aus der Methode herausspringt.

Zu den Methoden-Escapes gehören:

  • Übergeben Sie die Objektadresse an andere Methoden, indem Sie Parameter aufrufen.
  • Das Objekt gibt den Objektzeiger über die Return-Anweisung an andere Methoden zurück
  • usw.
  1. 我们可以用下面的代码来表示这个现象。
  2. //StringBuffer对象发生了方法逃逸
  3. public static StringBuffer createStringBuffer(String s1, String s2) {
  4. StringBuffer sb = new StringBuffer();
  5. sb.append(s1);
  6. sb.append(s2);
  7. return sb;
  8. }
  9. 上面的例子中,StringBuffer 对象通过return语句返回。
  10. StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。
  11. 甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
  12. 不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。
  13. 具体的代码如下:
  14. // 非方法逃逸
  15. public static String createString(String s1, String s2) {
  16. StringBuffer sb = new StringBuffer();
  17. sb.append(s1);
  18. sb.append(s2);
  19. return sb.toString();
  20. }
  21. 可以看出,想要逃逸方法的话,需要让对象本身被外部调用,或者说, 对象的指针,传递到了 方法之外。

Wie lässt sich schnell feststellen, ob eine Escape-Analyse stattgefunden hat? Schauen wir uns an, ob die neue Objektentität außerhalb der Methode aufgerufen wird.

  1. public class EscapeAnalysis {
  2.  
  3.     public EscapeAnalysis obj;
  4.  
  5.     /**
  6.      * 方法返回EscapeAnalysis对象,发生逃逸
  7.      * @return
  8.      */
  9.     public EscapeAnalysis getInstance() {
  10.         return obj == null ? new EscapeAnalysis():obj;
  11.     }
  12.  
  13.     /**
  14.      * 为成员属性赋值,发生逃逸
  15.      */
  16.     public void setObj() {
  17.         this.obj = new EscapeAnalysis();
  18.     }
  19.  
  20.     /**
  21.      * 对象的作用于仅在当前方法中有效,没有发生逃逸
  22.      */
  23.     public void useEscapeAnalysis() {
  24.         EscapeAnalysis e = new EscapeAnalysis();
  25.     }
  26.  
  27.     /**
  28.      * 引用成员变量的值,发生逃逸
  29.      */
  30.     public void useEscapeAnalysis2() {
  31.         EscapeAnalysis e = getInstance();
  32.     }
  33. }

2. Thread-Escape: Auf dieses Objekt wird von anderen Threads zugegriffen, z. B. einer Instanzvariablen zugewiesen und von anderen Threads darauf zugegriffen. Das Objekt ist dem aktuellen Thread entkommen.

Optimierungsstrategien für die Escape-Analyse

Die Escape-Analyse kann die folgenden Optimierungsstrategien in Java-Programme einbringen: Zuordnung auf dem Stapel, Eliminierung der Synchronisation, Skalarersatz und Methoden-Inlining;

Parameter für die Escape-Analyse:

  1. -XX:+DoEscapeAnalysis 开启逃逸分析
  2. -XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。
  3. -XX:+EliminateAllocations 开启标量替换
  4. -XX:+EliminateLocks 开启同步消除
  5. -XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况。

1. Stapelzuteilung

Die Escape-Analyse kann ermitteln, welche Objekte den Gültigkeitsbereich der Methode nicht verlassen, und diese Objekte auf dem Stapel statt auf dem Heap zuordnen. Auf dem Stapel zugewiesene Objekte werden innerhalb des Lebenszyklus des Methodenaufrufs ohne Speicherbereinigung erstellt und zerstört, wodurch die Effizienz der Programmausführung verbessert wird.

Unter normalen Umständen nehmen Objekte, die nicht entkommen können, relativ viel Platz ein. Wenn der Platz auf dem Stapel genutzt werden kann, wird am Ende der Methode eine große Anzahl von Objekten zerstört, wodurch der GC-Druck verringert wird.

        Zuordnungsideen auf dem StapelDie Zuordnung auf dem Stapel ist eine von der JVM bereitgestellte Optimierungstechnologie.
Die Idee ist:

  1. Thread-private Objekte (Objekte, auf die andere Threads nicht zugreifen können) können dem Stapelspeicher anstelle des Heapspeichers zugewiesen werden. Dies ist eine Lösung, um Aggregatvariablen durch Skalare zu ersetzen.
  2. Der Vorteil der Zuordnung auf dem Stapel besteht darin, dass er nach Ende der Methode automatisch zerstört werden kann, ohne dass ein GC-Eingriff erforderlich ist, wodurch die Systemleistung verbessert wird.
  3. Bei einer großen Anzahl verstreuter Objekte bietet die Zuweisung auf dem Stapel eine gute Strategie zur Objektzuweisung. Durch die Zuweisung von Geschwindigkeitsblöcken auf dem Stapel können die negativen Auswirkungen des GC-Recyclings wirksam vermieden werden.

Problem: Da der Stapelspeicher relativ klein ist, können und sind große Objekte nicht für die Allokation auf dem Stapel geeignet.
        Aktivieren Sie die Zuweisung auf dem Stapel
Die Zuordnung auf dem Stapel basiert auf Escape-Analyse und Skalarersetzung, daher müssen Escape-Analyse und Skalarersetzung aktiviert sein. Natürlich ist JDK1.8 standardmäßig aktiviert.

  1. 开启逃逸分析:-XX:+DoEscapeAnalysis
  2. 关闭逃逸分析:-XX:-DoEscapeAnalysis
  3. 显示分析结果:-XX:+PrintEscapeAnalysis
  4. 开启标量替换:-XX:+EliminateAllocations
  5. 关闭标量替换:-XX:-EliminateAllocations
  6. 显示标量替换详情:-XX:+PrintEliminateAllocations

Beispiel für die Zuordnung auf dem Stapel:

  1. 示例1
  2. import java.lang.management.ManagementFactory;
  3. import java.util.List;
  4. /**
  5. * 逃逸分析优化-栈上分配
  6. * 栈上分配,意思是方法内局部变量(未发生逃逸)生成的实例在栈上分配,不用在堆中分配,分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。
  7. * 一般生成的实例都是放在堆中的,然后把实例的指针或引用压入栈中。
  8. *虚拟机参数设置如下,表示做了逃逸分析 消耗时间在10毫秒以下
  9. * -server -Xmx10M -Xms10M
  10. -XX:+DoEscapeAnalysis -XX:+PrintGC
  11. *
  12. *虚拟机参数设置如下,表示没有做逃逸分析 消耗时间在1000毫秒以上
  13. * -server -Xmx10m -Xms10m
  14. -XX: -DoEscapeAnalysis -XX:+PrintGC
  15. * @author 734621
  16. *
  17. */
  18. public class OnStack{
  19. public static void alloc(){
  20. byte[] b=new byte[2];
  21. b[0]=1;
  22. }
  23. public static void main(String [] args){
  24. long b=System.currentTimeMillis();
  25. for(int i=0;i<100000000;i++){
  26. alloc();
  27. }
  28. long e=System.currentTimeMillis();
  29. System.out.println("消耗时间为:" + (e - b));
  30. List<String> paramters = ManagementFactory.getRuntimeMXBean().getInputArguments();
  31. for(String p : paramters){
  32. System.out.println(p);
  33. }
  34. }
  35. }
  36. 加逃逸分析的结果
  37. [GC (Allocation Failure) 2816K->484K(9984K), 0.0013117 secs]
  38. 消耗时间为:7
  39. -Xmx10m
  40. -Xms10m
  41. -XX:+DoEscapeAnalysis
  42. -XX:+PrintGC
  43. 没有加逃逸分析的结果如下:
  44. [GC (Allocation Failure) 3320K->504K(9984K), 0.0003174 secs]
  45. [GC (Allocation Failure) 3320K->504K(9984K), 0.0002524 secs]
  46. 消耗时间为:1150
  47. -Xmx10m
  48. -Xms10m
  49. -XX:-DoEscapeAnalysis
  50. -XX:+PrintGC
  51. 以上测试可以看出,栈上分配可以明显提高效率: 效率是不开启的1150/7= 160
  52. 示例2
  53. 我们通过举例来说明 开启逃逸分析 和 未开启逃逸分析时候的情况
  54. class User {
  55. private String name;
  56. private String age;
  57. private String gender;
  58. private String phone;
  59. }
  60. public class StackAllocation {
  61. public static void main(String[] args) throws InterruptedException {
  62. long start = System.currentTimeMillis();
  63. for (int i = 0; i < 100000000; i++) {
  64. alloc();
  65. }
  66. long end = System.currentTimeMillis();
  67. System.out.println("花费的时间为:" + (end - start) + " ms");
  68. // 为了方便查看堆内存中对象个数,线程sleep
  69. Thread.sleep(10000000);
  70. }
  71. private static void alloc() {
  72. // 未发生逃逸
  73. User user = new User();
  74. }
  75. }
  76. 设置JVM参数,表示未开启逃逸分析
  77. -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
  78. 花费的时间为:664 ms
  79. 然后查看内存的情况,发现有大量的User存储在堆中
  80. 开启逃逸分析
  81. -Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
  82. 然后查看运行时间,我们能够发现花费的时间快速减少,同时不会发生GC操作
  83. 花费的时间为:5 ms
  84. 在看内存情况,我们发现只有很少的User对象,说明User未发生逃逸,因为它存储在栈中,随着栈的销毁而消失。

Im Vergleich können wir sehen

  • Aktivieren Sie die Stapelzuweisung und weisen Sie nicht maskierte Objekte im Stapelspeicher zu, was offensichtlich effizienter ist.
  • Nach dem Schließen der Zuordnung auf dem Stapel führt der GC häufig eine Speicherbereinigung durch.

2. Sperrenbeseitigung

Die Escape-Analyse kann erkennen, dass auf bestimmte Objekte nur von einem einzelnen Thread zugegriffen wird und dass sie nicht in andere Threads entkommen. Daher können unnötige Synchronisationsvorgänge eliminiert und der Ausführungsaufwand von Multithread-Programmen reduziert werden.

Synchronisationssperren sind sehr leistungsintensiv. Wenn der Compiler also feststellt, dass ein Objekt nicht entkommen ist, entfernt er die Synchronisationssperre vom Objekt. JDK1.8 aktiviert standardmäßig Synchronisierungssperren, basiert jedoch auf der Aktivierung der Escape-Analyse.

  1. -XX:+EliminateLocks #开启同步锁消除(JVM默认状态)
  2. -XX:-EliminateLocks #关闭同步锁消除
  1. 通过示例: 明显可以看到“逃逸分析和锁消除” 对性能的提升
  2. public void testLock(){
  3. long t1 = System.currentTimeMillis();
  4. for (int i = 0; i < 100_000_000; i++) {
  5. locketMethod();
  6. }
  7. long t2 = System.currentTimeMillis();
  8. System.out.println("耗时:"+(t2-t1));
  9. }
  10. public static void locketMethod(){
  11. EscapeAnalysis escapeAnalysis = new EscapeAnalysis();
  12. synchronized(escapeAnalysis) {
  13. escapeAnalysis.obj2="abcdefg";
  14. }
  15. }
  16. 设置JVM参数,开启逃逸分析, 耗时:
  17. java -Xmx64m -Xms64m -XX:+DoEscapeAnalysis
  18. 设置JVM参数,关闭逃逸分析, 耗时:
  19. java -Xmx64m -Xms64m -XX:-DoEscapeAnalysis
  20. 设置JVM参数,关闭锁消除,再次运行
  21. java -Xmx64m -Xms15m -XX:+DoEscapeAnalysis -XX:-EliminateLocks
  22. 设置JVM参数,开启锁消除,再次运行
  23. java -Xmx64m -Xms15m -XX:+DoEscapeAnalysis -XX:+EliminateLocks

Die Kosten für die Thread-Synchronisierung sind recht hoch und die Folge der Synchronisierung ist eine verringerte Parallelität und Leistung.

Beim dynamischen Kompilieren eines synchronisierten Blocks kann der JIT-Compiler mithilfe der Escape-Analyse feststellen, ob auf das vom synchronisierten Block verwendete Sperrobjekt nur ein Thread zugreifen kann und es nicht für andere Threads freigegeben wurde. Wenn nicht, desynchronisiert der JIT-Compiler diesen Teil des Codes beim Kompilieren dieses synchronisierten Blocks. Dies kann die Parallelität und Leistung erheblich verbessern. Dieser Vorgang des Aufhebens der Synchronisation wird als Synchronisationsauslassung oder Sperrenbeseitigung bezeichnet.

  1. 例如下面的代码
  2. public void f() {
  3.     Object hellis = new Object();
  4.     synchronized(hellis) {
  5.         System.out.println(hellis);
  6.     }
  7. }
  8. 代码中对hellis这个对象加锁,但是hellis对象的生命周期只在f()方法中,并不会被其他线程所访问到,所以在JIT编译阶段就会被优化掉,优化成:
  9. public void f() {
  10.     Object hellis = new Object();
  11.     System.out.println(hellis);
  12. }
  13. 我们将其转换成字节码,此处发现,还是有同步锁的身影,是因为优化是在编译阶段的,在加载进内存后发生。

3. Skalarersatz

Die Escape-Analyse kann ein Objekt in mehrere Skalare aufteilen, z. B. primitive Typen oder andere Objekte, und diese an verschiedenen Orten zuweisen. Dies kann die Speicherfragmentierung und den Overhead beim Objektzugriff reduzieren und die Effizienz der Speichernutzung verbessern.

Zunächst müssen wir Skalare und Aggregate verstehen. Verweise auf Grundtypen und Objekte können als Skalare verstanden werden und können nicht weiter zerlegt werden. Die Menge, die weiter zerlegt werden kann, ist die Gesamtmenge, wie zum Beispiel: Objekt.

Das Objekt ist eine Gesamtgröße, die weiter in Skalare und ihre Mitgliedsvariablen in diskrete Variablen zerlegt werden kann. Dies wird als Skalarersetzung bezeichnet.

Wenn ein Objekt nicht entkommt, muss es überhaupt nicht erstellt werden. Nur die von ihm verwendeten Mitgliedsskalare werden auf dem Stapel oder Register erstellt, was Speicherplatz spart und die Anwendungsleistung verbessert.

Skalare Substitution ist in JDK1.8 ebenfalls standardmäßig aktiviert, sie muss jedoch auch auf einer aktivierten Escape-Analyse basieren.

Ein Skalar ist ein Datenwert, der nicht in kleinere Datenwerte zerlegt werden kann. Der primitive Datentyp in Java ist Skalar.

Im Gegensatz dazu werden Daten, die zerlegt werden können, als Aggregat bezeichnet. Ein Objekt ist in Java ein Aggregat, da es in andere Aggregate und Skalare zerlegt werden kann.

  1. public static void main(String args[]) {
  2.     alloc();
  3. }
  4. class Point {
  5.     private int x;
  6.     private int y;
  7. }
  8. private static void alloc() {
  9.     Point point = new Point(1,2);
  10.     System.out.println("point.x" + point.x + ";point.y" + point.y);
  11. }
  12. 以上代码,经过标量替换后,就会变成
  13. private static void alloc() {
  14.     int x = 1;
  15.     int y = 2;
  16.     System.out.println("point.x = " + x + "; point.y=" + y);
  17. }

Wenn in der JIT-Phase durch die Escape-Analyse festgestellt wird, dass die Außenwelt nicht auf ein Objekt zugreifen kann, wird das Objekt nach der JIT-Optimierung in mehrere darin enthaltene Mitgliedsvariablen zerlegt und ersetzt. Bei diesem Vorgang handelt es sich um eine Skalarersetzung.
Es ist ersichtlich, dass nach der Escape-Analyse festgestellt wurde, dass die Gesamtmenge Point nicht entkommen ist und daher durch zwei Skalare ersetzt wurde. Was sind also die Vorteile der Skalarsubstitution? Das heißt, es kann die Heap-Speichernutzung erheblich reduzieren. Denn sobald keine Notwendigkeit besteht, Objekte zu erstellen, besteht auch keine Notwendigkeit, Heap-Speicher zuzuweisen. Skalare Substitution bietet eine gute Grundlage für die Zuordnung auf dem Stapel.

Prüfung der Fluchtanalyse

  1. 逃逸分析测试
  2. 代码如下,大致思路就是 for 循环 1 亿次,循环体内调用外部的 allot() 方法,而 allot() 方法的作用就是简单创建一个对象,但是这个对象是内部的,所以是未逃逸的,所以理论上 JVM 是会进行优化的,我们拭目以待。并且我们会对比开启和关闭逃逸分析之后各自程序的运行时间:
  3. /**
  4. * @ClassName: EscapeAnalysisTest
  5. * @Description: http://www.jetchen.cn 逃逸分析 demo
  6. * @Author: Jet.Chen
  7. * @Date: 2020/11/23 14:26
  8. * @Version: 1.0
  9. **/
  10. public class EscapeAnalysisTest {
  11. public static void main(String[] args) {
  12. long t1 = System.currentTimeMillis();
  13. for (int i = 0; i < 100000000; i++) {
  14. allot();
  15. }
  16. long t2 = System.currentTimeMillis();
  17. System.out.println(t2-t1);
  18. }
  19. private static void allot() {
  20. Jet jet = new Jet();
  21. }
  22. static class Jet {
  23. public String name;
  24. }
  25. }
  26. 上面就是我们进行逃逸分析测试的代码, mian() 方法末尾有一个线程暂停,目的是为了观察此时 JVM 中的内存情况。
  27. Step 1:测试开启逃逸
  28. 由于环境是 jdk1.8,默认开启了逃逸分析,所以直接运行,得到结果如下,程序耗时 3 毫秒:
  29. 此时线程是处于睡眠状态的,我们观察下内存情况,发现堆内存中一共新建了 11 万个 Jet 对象。
  30. Step 2:测试关闭逃逸
  31. 我们关闭逃逸分析再来运行一次(使用 java -XX:-DoEscapeAnalysis EscapeAnalysisTest 来运行代码即可),得到结果如下,程序耗时 400 毫秒:
  32. 此时我们观察下内存情况,发现堆内存中一共新建了 3 千多万个 Jet 对象。
  33. 所以,无论是从代码的执行时间(3 毫秒 VS 400 毫秒),还是从堆内存中对象的数量(11 万个 VS 3 千万个)来分析,在上述场景下,开启逃逸分析是有正向益的。
  34. Step 3:测试标量替换
  35. 我们测试下开启和关闭 标量替换,如下图:
  36. 由上图我们可以看出,在上述极端场景下,开启和关闭标量替换对于性能的影响也是满巨大的,另外,同时也验证了标量替换功能生效的前提是逃逸分析已经开启,否则没有意义。
  37. Step 4:测试锁消除
  38. 测试锁消除,我们需要简单调整下代码,即给 allot() 方法中的内容加锁处理,如下:
  39. private static void allot() {
  40. Jet jet = new Jet();
  41. synchronized (jet) {
  42. jet.name = "jet Chen";
  43. }
  44. }
  45. 然后我们运行测试代码,测试结果也很明显,在上述场景下,开启和关闭锁消除对程序性能的影响也是巨大的。
  46. /**
  47. * 进行两种测试
  48. * 关闭逃逸分析,同时调大堆空间,避免堆内GC的发生,如果有GC信息将会被打印出来
  49. * VM运行参数:-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
  50. *
  51. * 开启逃逸分析 jdk8默认开启
  52. * VM运行参数:-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
  53. *
  54. * 执行main方法后
  55. * jps 查看进程
  56. * jmap -histo 进程ID
  57. *
  58. */
  59. @Slf4j
  60. public class EscapeTest {
  61. public static void main(String[] args) {
  62. long start = System.currentTimeMillis();
  63. for (int i = 0; i < 500000; i++) {
  64. alloc();
  65. }
  66. long end = System.currentTimeMillis();
  67. log.info("执行时间:" + (end - start) + " ms");
  68. try {
  69. Thread.sleep(Integer.MAX_VALUE);
  70. } catch (InterruptedException e1) {
  71. e1.printStackTrace();
  72. }
  73. }
  74. /**
  75. * JIT编译时会对代码进行逃逸分析
  76. * 并不是所有对象存放在堆区,有的一部分存在线程栈空间
  77. * Ponit没有逃逸
  78. */
  79. private static String alloc() {
  80. Point point = new Point();
  81. return point.toString();
  82. }
  83. /**
  84. *同步省略(锁消除) JIT编译阶段优化,JIT经过逃逸分析之后发现无线程安全问题,就会做锁消除
  85. */
  86. public void append(String str1, String str2) {
  87. StringBuffer stringBuffer = new StringBuffer();
  88. stringBuffer.append(str1).append(str2);
  89. }
  90. /**
  91. * 标量替换
  92. *
  93. */
  94. private static void test2() {
  95. Point point = new Point(1,2);
  96. System.out.println("point.x="+point.getX()+"; point.y="+point.getY());
  97. // int x=1;
  98. // int y=2;
  99. // System.out.println("point.x="+x+"; point.y="+y);
  100. }
  101. }
  102. @Data
  103. @AllArgsConstructor
  104. @NoArgsConstructor
  105. class Point{
  106. private int x;
  107. private int y;
  108. }

4. Methode Inlining

Durch die Escape-Analyse kann festgestellt werden, dass bestimmte Methodenaufrufe den Gültigkeitsbereich der aktuellen Methode nicht verlassen. Daher können diese Methoden inline optimiert werden, um die Kosten für Methodenaufrufe zu senken und die Ausführungseffizienz des Programms zu verbessern.

Durch diese Optimierungsstrategien kann die Escape-Analyse der JVM helfen, den Code besser zu optimieren, den Aufwand für die Speicherbereinigung zu reduzieren, die Effizienz und Reaktionsfähigkeit der Programmausführung zu verbessern und die Speichernutzung zu reduzieren.
 

Praktische Anwendungsszenarien

Die Escape-Analyse bietet eine breite Palette von Anwendungsszenarien in tatsächlichen Java-Anwendungen. Im Folgenden sind einige häufige Anwendungsszenarien aufgeführt:

  1. Wenn ein Objekt als Methodenparameter übergeben wird, kann die Escape-Analyse bestimmen, ob das Objekt maskiert ist, und somit bestimmen, ob das Objekt auf dem Heap oder Stack zugeordnet ist
    1. Wenn ein Objekt als Methodenrückgabewert verwendet wird, kann die Escape-Analyse bestimmen, ob das Objekt entkommt, und somit bestimmen, ob das Objekt auf dem Heap oder Stack zugeordnet ist.
    1. Wenn ein Objekt von Threads gemeinsam genutzt wird, kann die Escape-Analyse ermitteln, ob das Objekt maskiert ist, und somit feststellen, ob Synchronisierungsvorgänge erforderlich sind.
    1. Wenn ein temporäres Objekt in einer Schleife erstellt wird, kann die Escape-Analyse ermitteln, ob das Objekt entkommt, und somit feststellen, ob das Objekt häufig erstellt und zerstört werden muss.

Nachteile der Escape-Analyse

Das Papier zur Escape-Analyse wurde 1999 veröffentlicht, aber erst in JDK1.6 implementiert, und diese Technologie ist noch nicht sehr ausgereift.

Der Hauptgrund dafür ist, dass es keine Garantie dafür gibt, dass der Leistungsverbrauch der Escape-Analyse höher ist als der Leistungsverbrauch. Obwohl die Escape-Analyse Skalarsubstitution, Stapelzuweisung und Sperrenbeseitigung durchführen kann. Allerdings erfordert die Fluchtanalyse selbst auch eine Reihe komplexer Analysen, was eigentlich ein relativ zeitaufwändiger Prozess ist.

Ein extremes Beispiel ist, dass nach der Escape-Analyse festgestellt wird, dass kein Objekt nicht entkommt. Dann ist der Prozess der Escape-Analyse umsonst.

Obwohl diese Technologie noch nicht sehr ausgereift ist, ist sie auch ein sehr wichtiges Mittel in der Just-in-Time-Compiler-Optimierungstechnologie. Mir ist aufgefallen, dass es einige Meinungen gibt, dass die JVM durch Escape-Analyse Objekte auf dem Stapel zuordnet, die nicht entkommen. Dies ist theoretisch möglich, hängt jedoch von der Wahl des JvM-Designers ab. Soweit ich weiß, macht Oracle Hotspot JVM dies nicht. Dies wurde in den Dokumenten zur Escape-Analyse erläutert, sodass klar ist, dass alle Objektinstanzen auf dem Heap erstellt werden.

Derzeit basieren viele Bücher noch auf Versionen vor JDK7. Der Cache für interne Zeichenfolgen und statische Variablen wurde einst der permanenten Generation zugewiesen und die permanente Generation wurde durch den Metadatenbereich ersetzt. Der interne String-Cache und die statischen Variablen werden jedoch nicht in den Metadatenbereich übertragen, sondern direkt auf dem Heap zugewiesen, sodass dies auch mit der Schlussfolgerung des vorherigen Punktes übereinstimmt: Objektinstanzen werden auf dem Heap zugewiesen. Das obige Beispiel wird aufgrund der Skalarsubstitution beschleunigt.

Vorteile der Escape-Analyse


Wenn ein Objekt nicht innerhalb des Methodenkörpers oder innerhalb des Threads entkommt (oder nach der Escape-Analyse festgestellt wird, dass es nicht entkommen konnte), können die folgenden Optimierungen vorgenommen werden:

Zuordnung auf Stapel:


Unter normalen Umständen nehmen Objekte, die nicht entkommen können, relativ viel Platz ein. Wenn der Platz auf dem Stapel genutzt werden kann, wird am Ende der Methode eine große Anzahl von Objekten zerstört, wodurch der GC-Druck verringert wird.


Synchrone Eliminierung:


Wenn für die Methode der von Ihnen definierten Klasse eine Synchronisationssperre vorhanden ist, aber zur Laufzeit nur ein Thread darauf zugreift, wird der Maschinencode nach der Escape-Analyse ohne die Synchronisationssperre ausgeführt.


Skalare Substitution:


Die primitiven Datentypen in der Java Virtual Machine (numerische Typen wie int, long und Referenztypen usw.) können nicht weiter zerlegt werden und können als Skalare bezeichnet werden. Im Gegensatz dazu wird ein Datenelement, das weiter zerlegt werden kann, als Aggregat bezeichnet. Das typischste Aggregat in Java ist ein Objekt. Wenn die Escape-Analyse beweist, dass auf ein Objekt nicht extern zugegriffen wird und dass das Objekt zerlegbar ist, wird das Objekt möglicherweise nicht erstellt, wenn das Programm tatsächlich ausgeführt wird, sondern es werden stattdessen direkt mehrere seiner Mitgliedsvariablen erstellt, die von dieser Methode zum Ersetzen verwendet werden. Die zerlegten Variablen können separat analysiert und optimiert werden, sodass keine Beziehungen über Referenzzeiger hergestellt werden müssen. Sie können kontinuierlich und kompakt gespeichert werden, was die Datenverarbeitung erleichtert Ausführung führt zu Leistungseinbußen. Gleichzeitig können Sie auch Platz auf dem Stapelrahmen bzw. Register zuweisen, sodass das ursprüngliche Objekt nicht als Ganzes Platz zuweisen muss.

Zusammenfassung


Die junge Generation ist der Bereich, in dem Objekte geboren werden, wachsen und sterben. Hier wird ein Objekt erzeugt und verwendet, das schließlich vom Müllsammler eingesammelt wird und sein Leben beendet.

Objekte mit langen Lebenszyklen, die in der alten Generation platziert werden, sind normalerweise Java-Objekte, die aus dem Survivor-Bereich kopiert wurden. Natürlich gibt es auch Sonderfälle: Wenn das Objekt groß ist, versucht die JVM, es direkt anderen Orten in Eden zuzuordnen Um einen ausreichend langen, kontinuierlichen freien Speicherplatz im Speicherplatz der neuen Generation zu finden, weist die JVM ihn direkt der alten Generation zu. Wenn GC nur in der jungen Generation auftritt, wird der Vorgang des Recyclings von Objekten der jungen Generation als MinorGc bezeichnet.

Wenn GC in der alten Generation vorkommt, wird es MajorGc oder FullGC genannt. Im Allgemeinen ist die Häufigkeit des Auftretens von MinorGc viel höher als die von MajorGC, was bedeutet, dass die Häufigkeit der Speicherbereinigung in der alten Generation viel geringer sein wird als in der jungen Generation.

Die JVM-Escape-Analyse verwendet zwei Analysemethoden, statisch und dynamisch, um zu bestimmen, ob ein Objekt den Gültigkeitsbereich einer Methode verlassen kann. Es kann der JVM helfen, den Code zu optimieren und die Leistung und Speichernutzungseffizienz von Java-Programmen zu verbessern.

Zu den Optimierungsstrategien für die Escape-Analyse gehören On-Stack-Zuweisung, Synchronisationseliminierung, Skalarsubstitution und Methoden-Inlining. Diese Optimierungsstrategien können den Overhead der Garbage Collection reduzieren, die Effizienz und Reaktionsfähigkeit der Programmausführung verbessern und die Speichernutzung reduzieren.

beziehen auf:

https://zhuanlan.zhihu.com/p/693382698

JVM-Heap-Escape Analysis-08-CSDN Blog

JIT-Speicher-Escape-Analyse_java deaktiviert Skalarersatz – CSDN-Blog

Alle konfigurierten Parameterwerte der JVM anzeigen

java -XX:+PrintFlagsFinal  #输出打印所有参数jvm参数

Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein