Codeoptimierung durch JIT-Compiler
Wenn eine Methode zur Kompilierung ausgewählt wird, führt die JVM ihre Bytecodes dem JIT-Compiler (Just-in-time-Compiler) zu. Der JIT-Compiler muss die Semantik und Syntax der Bytecodes verstehen, um die Methode ordnungsgemäß kompilieren zu können.
Um dem JIT-Compiler bei der Analyse der Methode zu helfen, werden die Bytecodes zunächst in eine interne Darstellung namens Bäume umformuliert, die eher an Maschinencode als an Bytecodes erinnert. Analyse und Optimierungen werden dann an den Bäumen der Methode ausgeführt. Zum Schluss werden die Bäume in nativen Code übersetzt. Nachfolgend erhalten Sie einen kurzen Überblick über die Phasen der JIT-Kompilierung. Weitere Informationen finden Sie unter JIT-oder AOT-Problem diagnostizieren.
Der JIT-Compiler kann mehr als einen Kompilierungsthread verwenden, um JIT-Kompilierungstasks durchzuführen. Durch die Verwendung mehrerer Threads können Java-Anwendungen möglicherweise schneller gestartet werden. In der Praxis erzielen mehrere JIT-Kompilierungsthreads nur dann Leistungsverbesserungen, wenn es nicht verwendete Verarbeitungskerne im System gibt.
Die Kompilierung teilt sich in folgende Phasen auf. Alle Phasen, ausgenommen der Generierung von nativem Code, beziehen sich auf plattformübergreifenden Code.
Phase 1 – Inlining
- Trivial Inlining
- Call Graph Inlining
- Tail Recursion Elimination
- Virtual Call Guard-Optimierungen
Phase 2 – Lokale Optimierungen
- Lokale Datenflussanalysen und -optimierungen
- Optimierung der Registernutzung
- Vereinfachungen von Java-Idiomen
Phase 3 – Steuerungsablaufoptimierungen
- Code umstellen, teilen und entfernen
- Loop-Reduction und Loop-Inversion
- Loop-Striding und Verschieben von schleifeninvariantem Code
- Loop-Unrolling und Loop-Peeling
- Loop-Versioning und Loop-Specialization
- Durch Ausnahmebedingungen gesteuerte Optimierung
- Switch-Analyse
Phase 4 – Globale Optimierungen
- Globale Datenflussanalysen und -optimierungen
- Partieller Redundanzausschluss
- Escape-Analysen
- GC- und Speicherzuordnungsoptimierungen
- Synchronisationsoptimierungen
Phase 5 – Generierung von nativem Code
Die Prozesse zur Generierung von nativem Code variieren je nach Plattformarchitektur. Im Allgemeinen werden während dieser Kompilierungsphase die Bäume einer Methode in Maschinencodeanweisungen übersetzt und einige kleinere Optimierungen werden entsprechend den Architekturmerkmalen vorgenommen. Der kompilierte Code wird in einem Teil des JVM-Prozessraums abgelegt, der als Code-Cache bezeichnet wird. Die Position der Methode wird aufgezeichnet, sodass zukünftige Aufrufe der Methode den kompilierten Code aufrufen. Zu jedem beliebigen Zeitpunkt besteht der JVM-Prozess aus den ausführbaren JVM-Dateien und einem Satz JIT-kompilierten Codes, der dynamisch mit dem Bytecode-Interpreter in der JVM verknüpft ist.