Aus Linux-Magazin 07/2020

Was die GraalVM Java-Entwicklern bringt

© Luis Louro, 123RF

Mit GraalVM stellt Oracle eine neue Umgebung zum Kompilieren und Ausführen von Programmen vor. Der kühn gewählte Name suggeriert, dass sie alles besser und schneller macht als bestehende virtuelle Maschinen wie OpenJDK, Node.js oder Python. Ein erster Blick zeigt, ob die Suche nach dem heiligen Gral nun enden darf.

Mit der Hotspot-Architektur für die virtuelle Maschine legte Sun vor über 20 Jahren eine solide Grundlage für den anhaltenden Erfolg von Java. Sie ist immer weiter gereift und hat an Fähigkeiten und Tempo gewonnen. Ihre Beschränkungen zeigen sich aber gerade in Randbereichen wie beim Einsatz auf extrem minimaler oder leistungsstarker Hardware oder bei der Integration mit anderen Sprachen.

Das erklärt auch den zunehmenden Markterfolg alternativer Laufzeitumgebungen wie Androids ART für Java oder Node.js für Javascript. In der Folge begann Oracles Forschungsabteilung vor einigen Jahren, virtuelle Maschinen weiterzudenken.

Das Ergebnis steht nun unter dem etwas anmaßenden Namen GraalVM [1] bereit, und der Hersteller bewirbt es mit allerlei vollmundigen Versprechungen. Doch statt ewiger Jugend, Glückseligkeit und unendlichen Speisen sind die für GraalVM geäußerten Verheißungen eher technischer Natur [2]. Sie umfassen unter anderem ein schnelleres Starten und Ausführen von Java-Code, ein nahtloses Kombinieren sowohl mit Interpreter- (wie Javascript, Ruby, Python) als auch Compiler-Sprachen (etwa C, C++, Fortran) sowie die Möglichkeit, native Anwendungen mit Java-Code zu erzeugen.

Die VM-Hersteller haben dafür die klassische Hotspot-Architektur um zusätzliche Komponenten erweitert (Abbildung 1). Das Kernstück ist der Graal-Compiler, eine Alternative zu den beiden in der Hotspot-VM enthaltenen Just-in-Time-Compilern C1 und C2.

Abbildung 1: Die Architektur von GraalVM erlaubt unter anderem über einen Seitenweg das Erzeugen von Native Images.

Abbildung 1: Die Architektur von GraalVM erlaubt unter anderem über einen Seitenweg das Erzeugen von Native Images.

Letztere sollten Entwickler nicht mit dem Compiler »javac« verwechseln, der maschinenunabhängigen Bytecode auf Basis von Java-Quellcode erzeugt. Die Rollen von C1 und C2 bestehen vielmehr darin, ausführbaren Code auf Basis des Bytecodes aus den ».class«-Dateien zu generieren.

Der Graal-Compiler möchte an zwei Stellen besser sein: Er will erstens eine höhere Optimierung erreichen und zweitens mit einem flexibleren internen Aufbau punkten. Die alten Compiler gibt es seit Java-1.3-Zeiten, also seit dem Jahr 2000. Mit neueren Konstrukten wie Lambdas, Streams oder gar solchen aus Interpreter-Sprachen wie Python oder Javascript kommen sie nicht so gut zurecht. Der neue Compiler verspricht hingegen vor allem bei aktuellem Java-Code bessere Performance.

Um dies möglichst einfach zu erreichen, ist der Graal-Compiler selbst in Java geschrieben. Java-Entwickler passen ihn daher einfacher an als die in C++ implementierten C1- und C2-Compiler. Die Kommunikation findet dabei über das Java Virtual Machine Compiler Interface (JVMCI, [3]) statt, mit dem Compiler an die Laufzeit andocken. Den Bytecode aus ».class«-Dateien optimiert der Graal-Compiler in der neuen Architektur zunächst, bevor er ihn zum Ausführen über das JVMCI an die Hotspot-VM weiterreicht.

Dabei ist das Laden von Bytecode nicht der einzige Weg, Code in den Graal-Compiler zu bringen. Mit dem Truffle-Framework lassen sich Werkzeuge entwickeln, um Code aus anderen Sprachen zu laden. Solche Ladewerkzeuge gibt es bereits für Interpreter-Sprachen wie Javascript, Python, Ruby oder R. Zwar laufen diese Sprachen schon seit Jahren in der Java-VM, die Vorgehensweise ist dabei aber komplett anders.

In der Vergangenheit implementierten die Entwickler den Interpreter (zum Beispiel für Javascript [4]) in Java und führten den Javascript-Code aus. Das Truffle-Framework bereitet den Javascript-Code hingegen für den Graal-Compiler auf, und der führt ihn dann ähnlich wie Java-Code aus. Daher sollen diese Sprachen signifikant schneller laufen als mit dem Interpreter-Ansatz.

Das Entwickeln solcher Ladewerkzeuge gelingt recht einfach, wie ein Beispiel [5] demonstriert, das eine eigene Sprache auf Basis von Truffle lädt. Das Laden beschränkt sich dabei nicht nur auf den lesbaren Quelltext, der Bitcode-Interpreter Sulong lädt vielmehr den Bitcode aus dem LLVM-Compiler [6]. Damit kommen der bewährte Präprozessor und Parser aus LLVM zum Einsatz, um Sprachen wie C, C++, Fortran oder Ada in der Hotspot-VM auszuführen.

Da der Graal-Compiler dieselbe interne Struktur für alle Sprachen nutzt, optimiert er beispielsweise Ruby analog zu Java. Damit setzt er sich sogar über Sprachgrenzen hinweg: Die potenziell in unterschiedlichen Sprachen geschriebenen Teile mehrsprachiger Anwendungen analysiert und optimiert GraalVM nach dem Laden als Gesamtkonstrukt. Doch egal, ob der geladene Code aus Java-, Javascript- oder C-Quellen stammt, letztendlich läuft alles auf der virtuellen Maschine. Es handelt sich also nicht um ein natives Kompilat.

Anders verhält sich das hingegen bei der in Abbildung 1 gezeigten Abzweigung. Hier erzeugt der Graal-Compiler Code, der über die normale C-Compiler-Schiene eine nativ kompilierte Version des Programms generiert, ein Native Image. Die sonst über die Java-VM angebotenen Funktionen und Laufzeitbibliotheken stellt hier eine native Bibliothek namens Substrate-VM bereit.

Praxis

Die Verheißungen der GraalVM fallen damit wirklich umfangreich aus. Bleibt zu klären, was davon im Programmieralltag übrigbleibt. Anwender laden GraalVM von der Projektwebseite herunter [1]. Dieser Artikel bezieht sich auf die Community Edition auf Basis von Java 11, die auch auf der Heft-DVD zu finden ist. Daneben gibt es noch weitere Versionen auf Basis von Java 8 sowie eine Enterprise Edition mit kostenpflichtigem Support von Oracle. Die Community Edition und deren Quellen stehen unter derselben Lizenz wie Java selbst, also der GPLv2 mit Classpath-Ausnahme. Das erlaubt den kostenlosen Einsatz für freie und kommerzielle Software.

Die GraalVM-Installation bietet zunächst einmal einen vollständigen Ersatz für eine herkömmliche Java-Installation und bringt dieselben Werkzeuge zum Kompilieren und Ausführen von Java-Code mit. Das Basis-Paket hat noch eine Laufzeitumgebung für Javascript im Gepäck. Zusätzliche Features zum Laden von Python-Programmen oder zum Erzeugen nativer Programme mit dem Werkzeug »native-image« installiert das Graal-Update-Werkzeug Gu (Abbildung 2).

Abbildung 2: Die mit GraalVM erzeugten Native Images punkten mit kürzeren Startzeiten.

Abbildung 2: Die mit GraalVM erzeugten Native Images punkten mit kürzeren Startzeiten.

Wer GraalVM als Alternative zum normalen OpenJDK nutzen möchte, muss abhängig von der Entwicklungs- und Laufzeitumgebung die »JAVA_HOME«- und »PATH«-Variablen anpassen. IDEs ergänzt GraalVM einfach als weitere Java-Version.

Da der Java-Bytecode in ».class«- und ».jar«-Dateien zwischen OpenJDK und GraalVM kompatibel ist, muss der Programmierer keine Nebeneffekte befürchten. Die Klassen zum Ausführen von Javascript- oder Python-Code warten im Extramodul »org.graalvm.truffle«, das der Entwickler zusätzlich beim Classpath der IDEs angeben muss (Abbildung 3). Anders als bei normalen JAR-Dateien benötigen einige IDEs einen Neustart, bevor sie die Module integrieren und die Code-Completion für Klassennamen und Importe funktioniert.

Abbildung 3: Der Entwickler muss die IDE mit dem zusätzlichen Java-Modul »org.graalvm.truffle« bekanntmachen.

Abbildung 3: Der Entwickler muss die IDE mit dem zusätzlichen Java-Modul »org.graalvm.truffle« bekanntmachen.

Der erste Test gilt der versprochenen Optimierung des Graal-Compilers. In den Beispielquellen [1] findet sich ein mehrstufiges »map-reduce«-Beispiel, das diese demonstrieren soll. Schon durch den Einsatz der neuen Laufzeit läuft der Code tatsächlich 10 bis 15 Prozent schneller als mit dem normalen OpenJDK. Das Beispiel verwendet komplett Streams und Lambdas und ist damit perfekt auf den neuen Compiler zugeschnitten. Versuche mit eigenem Code aus den letzten Jahren zeigen Laufzeitunterschiede von -20 bis +5 Prozent.

An einfachen For-Schleifen oder der Kommunikation mit dem Dateisystem, den Datenbanken oder dem Netzwerk gibt es allerdings nicht viel zu optimieren. Berechnung, Mappen oder Filter gewinnen am ehesten. Das weckt natürlich Interesse, denn für eine Beschleunigung von 10 Prozent müssen Entwickler in der Regel schon echt kämpfen. Wenn das über einen simplen Wechsel der Laufzeitumgebung gelingt, ist es definitiv einen Versuch wert.

Im Zeitalter von Container-Virtualisierung und sekundengenau abgerechneten Laufzeiten beim Hoster spielen kürzere Startzeiten und ein geringerer Speicherverbrauch eine viel größere Rolle als noch vor fünf Jahren. Einen ersten Schritt ging das Projekt Jigsaw und führte mit Java 9 die modularen Bibliotheken ein. Damit lässt sich eine schlankere Laufzeitumgebung einsetzen, die damit die Startzeit reduziert. Einen ganz anderen Weg bietet der erwähnte Nebenausgang, um nativ kompilierten Code zu erzeugen. Das »native-image«-Kommando nimmt normalen Java-Bytecode entgegen und erzeugt eine nativ kompilierte Anwendung.

Abbildung 2 zeigt, wie zunächst ein normales OpenJDK ein Beispielprogramm kompiliert und ausführt. Anschließend installiert Gu die Erweiterung »native-image«. Zusätzlich benötigt der Entwickler die Linux-Pakete gcc, glibc-devel und zlib-devel. Einige Suse-Systeme brauchen zudem noch einen symbolischen Link von der »/lib64/libz.so.1« auf die »/lib/libz.so«.

Das »native-image«-Werkzeug nimmt die Datei »TopTen.java« [2] und erzeugt daraus ein natives Programm namens »topten«. Das »time«-Kommando misst dabei die Laufzeiten und den Speicherbedarf. Hier hat die nativ kompilierte Version klar die Nase vorn. Die Anwendung startet und beendet ihre Arbeit vier Mal schneller, derselbe Faktor ergibt sich beim Hauptspeicherbedarf.

Diese drastische Verbesserung hat jedoch ihren Preis: Java-Features wie das dynamische Laden von Klassen, Dynamic Proxy Classes und Reflection stehen im nativ kompilierten Code nur eingeschränkt bereit. Auch wenn nur wenige Entwickler diese Features selbst nutzen, kommen sie jedoch häufig über Bande zum Einsatz, etwa bei konfigurierbaren Bibliotheken wie beim Logging. Wer das in sein Programm oder in die Bibliotheken einbaut, muss dem Native Image mit Zusatzinformationen auf die Sprünge helfen.

Damit das Ermitteln dieser Informationen und das Kompilieren einfacher von der Hand gehen, gibt es inzwischen Support für das Erzeugen von Native Images in Build-Werkzeugen wie Maven. Frameworks wie Spring oder JavaFX sind ebenfalls für den Einsatz angepasst.

Mix and Match

Das Truffle-Framework stellt zudem in Aussicht, künftig weitere Sprachen auf der Hotspot-VM auszuführen. Aktuell gibt es Support für Javascript, Python, R, Ruby und Scala. Ein einfaches Beispiel zeigt Listing 1, das aus Java heraus Quelltext in den Gastsprachen Javascript und Python ausführt. Dabei ermöglicht das Binding einen bidirektionalen Austausch von Werten und sogar den Zugriff auf statische Felder und Methoden (Zeilen 8 und 12). Das ist ziemlich genial, da es den Einsatz von Quelltext aus diesen Sprachen ermöglicht.

Listing 1

Polyglotte Programmierung mit GraalVM

org.graalvm.polyglot.Context polyglot =
             org.graalvm.polyglot.Context.create();
// Javascript
Value jsBind = polyglot.getBindings("js");
jsBind.putMember("eingabe", "hinein");
String jsScript =  "console.log( 'Ausgabe aus Javascript\t' + eingabe );\n"+
                   "var rückgabe = 'hinaus';";
polyglot.eval("js",jsScript);
String rückgabe = jsBind.getMember("rückgabe").asString();
System.out.println("Ausgabe aus Java\t" + rückgabe);
// Python
String pyScript = "import sys\n" +
                  "print( sys.version_info)";
polyglot.eval("python",pyScript);

Fairerweise sei an dieser Stelle auch erwähnt, dass dies für Javascript bereits mit der eingebauten Javascript-Engine Nashorn klappte. Die unterstützt jedoch nur die alte ECMAScript-Version 5.1 von 2011, während GraalVM topaktuell ECMAScript 2020 beherrscht und denselben Javascript-Code fast doppelt so schnell ausführt.

Als Beweis dafür, was mit GraalVM geht, liegt ihr eine GraalVM-basierte Version der beliebten Node.js-Javascript-Runtime bei. Die ersetzt Googles V8-Javascript-Interpreter durch den Graal-Compiler, den Paketmanager Npm gibt es obendrauf.

Das ist nicht nur ein vollwertiger Ersatz für das normale Node.js: Dank der polyglotten Programmierung lassen sich aus Javascript alle von GraalVM unterstützten Sprachen aufrufen, darunter Java und Python. Oracle will die Kompatibilität regelmäßig mit 100 000 Paketen aus dem Npm-Register testen, und dank der 17 Millionen JAR-Dateien auf Maven Central sollte sich für das ein oder andere Problem eine fertige Lösung finden lassen.

Das Ganze klappt dabei nicht nur für diese Interpreter-Sprachen. Dank der Sulong-Bibliothek integrieren Entwickler auch C- und C++-Quellen in die virtuelle Maschine. Das erfordert zwei Compiler-Schritte: einen für den C/C++-Code mit Clang, einen zweiten für den Java-Code. Zur Laufzeit lädt Sulong dann den Bytecode aus Clang und führt den Java-Code aus.

Die oben für Javascript und Java beschriebenen gegenseitigen Aufrufe klappen auch hier, allerdings mit Einschränkungen: Stößt der verwendete C- und C++-Code auf falsche Speicheradressen, fällt dem Entwickler das Gesamtkunstwerk genauso auf die Füße wie sonst bei C und C++ auch. Die Technik, um diese Fehler in eine normale Java-Exception zu verwandeln, steckt nur in der Enterprise Edition der GraalVM.

Fazit

Mit der GraalVM hat Oracle tatsächlich ein architektonisches Juwel vorgelegt. Von der schnelleren Laufzeit bis hin zu den Native Images ist für viele Anwender etwas dabei. Der Support für Native Images ist aber mit Abstand das ambivalenteste Feature.

Wer den Weg aus den Java-Quellen in native Anwendungen geht, wird wieder deutlich erinnert, wie viel einfacher die Programmierung in Java ist als in C und C++. Viele Details, wie das Laden aus Ressourcen, die gewöhnlich Java ermittelt, muss der Programmierer nun selbst herausfinden und den Werkzeugen übermitteln. Andernfalls gibt es Compiler- oder Laufzeitfehler.

Die Sulong-Bibliothek erspart dem Entwickler zwar einige Schmerzen, andere Bereiche wie die Reflection, das Laden aus Java-Ressourcen und so weiter funktionieren jedoch nicht von vornherein. Das Native Image enthält zwar die notwendigen Werkzeuge, um diese Probleme zu lösen, aktuell ist das dennoch ein steiniger Weg. Den Lohn erhält der Entwickler in Form blitzschneller Startzeiten und eines geringeren Speicherbedarfs.

Den greifbarsten Vorteil bringt die GraalVM mit den Optimierungen und der polyglotten Programmierung. Diese lassen sich mühelos verwenden; es sind keine Änderungen am vorhandenen Code, an Bibliotheken oder Entwicklungsumgebungen notwendig.

Damit taugt GraalVM tatsächlich als attraktive Weiterentwicklung der Java-VM und spricht alle Java-Entwickler an. Ob die alternative Node.js-Umgebung hingegen viele Anwender findet, sei dahingestellt. Schließlich macht das alleinstehende Node.js einen guten Job für seine Anwender. Lediglich die Suche nach speziellen Java-Bibliotheken könnte einen Grund bieten. (kki)

Der Autor

Carsten Zerbst erstellt mit seinem Team Individualsoftware für die Automobil-, Luft- und Raumfahrtindustrie sowie den Schiffbau. Er sucht weitere Mitarbeitende für abwechslungsreiche Tätigkeiten mit Java, C# und Typescript mitten in Hamburg.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
1 Kommentar
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Frank Finner
6 Jahre her

Angeregt vom Artikel über die GraalVM habe ich sie endlich mal ausprobiert. Bislang war ich doch recht skeptisch ob der Ankündigungen. Also die community edition (Java8) geholt, ausgepackt, JAVA_HOME umgebogen und einen uralten JBoss 5 damit gestartet, ohne sonst irgend etwas zu konfigurieren oder ändern. Er lief schon mal recht zügig hoch, verglichen mit der üblichen Startzeit. Dann ein paar Tests mit der darauf installierten, recht umfangreichen Webstart-Applikation laufen lassen. Dann die Kinnlade fallen gelassen und leise “Wow!” gemurmelt. Reproduzierbare, gemessene 15-20% Performanceverbesserung out of the box, bei deutlich geringerem Speicherbedarf und volle Kompatibilität. Dafür muß ein alter Entwickler recht… Mehr »

Nach oben