Ein Just-in-Time-Compiler für Java


Thomas Kotzmann

Java-Programme werden in der Regel in maschinenunabhängigen Bytecode übersetzt und von einem Interpreter ausgeführt. Da die Interpretation wesentlich langsamer ist als die Ausführung eines Maschinenprogramms, werden die Bytecodes jedoch oft unmittelbar vor ihrer Ausführung in Maschinencode übersetzt. Man nennt dies Just-in-Time-Compilation (JIT-Compilation).

Thema dieser Diplomarbeit ist die Portierung eines existierenden JIT-Compilers für Java-Bytecodes, der von Sun Microsystems im Rahmen des HotSpot-Projekts entwickelt wurde. Der Compiler ist in C++ geschrieben und soll nach Java portiert werden. Eines der Ziele dieser Portierung ist ein Laufzeit-Vergleich zwischen der C++- und der Java-Version des Compilers.

Der Ablauf der JIT-Compilation ist wie folgt: Das auszuführende Programm wird vom Interpreter geladen und gestartet. Während seiner Ausführung werden Zähler mitgeführt, die diejenigen Methoden ermitteln, welche am häufigsten aufgerufen werden oder Schleifen mit hohen Durchlaufzahlen enthalten. Diese "Hot Spot"-Methoden werden dem JIT-Compiler übergeben, der sie in Maschinencode übersetzt. Beim nächsten Aufruf einer solchen Methode wird sie dann nicht mehr interpretiert sondern in Maschinencode ausgeführt.

Der jetzige Compiler ist in die Virtuelle Maschine (Interpreter, Garbage-Collector, Thread-System) von Java eingebettet und hat zahlreiche Schnittstellen zu ihr. Der neue Compiler soll von der Virtuellen Maschine entkoppelt sein und als eigenständiges Programm laufen. Die einzige Schnittstelle zur Umgebung soll der Bytecode und die Metainformation der zu übersetzenden Methode sein. Die Vorgehensweise bei der Portierung ist wie folgt:

  • Studieren Sie den existierenden Compiler und überlegen Sie sich, an welchen Stellen einzugreifen ist, um ihn aus der Virtuellen Maschine herauszulösen.
  • Ermitteln Sie (z.B. mittels Reflection) die Bytecodes einer geladenen Methode sowie weitere nötige Informationen wie Anzahl der Parameter und lokalen Variablen.
  • Bauen Sie wie im existierenden Compiler aus den Bytecodes der Methode einen Kontrollflußgraphen auf und übersetzen Sie die Bytecode-Instruktionen in eine Zwischensprache einer Registermaschine.
  • Führen Sie die bereits vorhandenen Optimierungen wie Inlining, Load/Store-Elimination und Konstantenfaltung durch.
  • Erzeugen Sie Maschinencode für die Intel x86-Familie.

Achten Sie bei der Portierung auf größtmögliche Effizienz Ihres Compilers. Verwenden Sie die gegenüber C++ verbesserten objektorientierten Eigenschaften von Java, versuchen Sie aber, die Architektur des ursprünglichen Compilers im wesentlichen beizubehalten.

Betreuer: Prof. Dr. H. Mössenböck
Ausgabe: 1. Oktober 2001