© StonePictures/Shutterstock.com
Native Java-Programme auf der GraalVM

Was steckt hinter den Frameworks?


GraalVM und SubstrateVM, native Java-Programme: Schlagworte, die im Moment in ganz vielen Artikeln die Runde machen. Worum geht es da?

GraalVM

Die GraalVM ist eine hochperformante Runtime, die laut Webseite des Herstellers Oracle signifikante Performanceverbesserungen für Anwendungen und erheblich effizientere Microservices verspricht: „GraalVM is a high-performance runtime that provides significant improvements in application performance and efficiency which is ideal for microservices. It is designed for applications written in Java, JavaScript, LLVM-based languages.“ [1]

Sowohl ein neuer und verbesserter Just-in-Time-(JIT)-Compiler als auch ein Ahead-of-Time-(AOT-)Compiler werden erwähnt. Beim neuen JIT-Compiler wird bereits versprochen, dass er verschiedene Szenarien im Vergleich zum Standard-JDK deutlich beschleunigt.

In obiger Quelle heißt es dann weiter: „For existing Java applications, GraalVM can provide benefits by […] creating ahead-of-time compiled native images.“ Während das erstmal sehr geschwollen klingt, heißt es nichts anderes, als dass die GraalVM aus existierenden Java-Programmen EXE-Executables bzw. ELF-Programme erzeugen kann. Das GraalVM-Modul hinter diesem Feature ist die SubstrateVM.

SubstrateVM

Die SubstrateVM ist das Subsystem der GraalVM, das letzten Endes als Teil eines nativen Images selbiges ausführt und die Laufzeitumgebung repräsentiert. Aus dem entsprechenden Readme:

„(A native image) does not run on the Java VM, but includes necessary components like memory management and thread scheduling from a different virtual machine, called „Substrate VM“. Substrate VM is the name for the runtime components (like the deoptimizer, garbage collector, thread scheduling etc.). The resulting program has faster startup time and lower runtime memory overhead compared to a Java VM.“ [2]

Auf der Seite des GraalVM-Teams finden sich einige Benchmarks, die die Vorteile zeigen, die sich ergeben, wenn zum Beispiel Java Microservices als native Programme ausgeführt werden. Dazu gehört der geringere Speicherbedarf, aber insbesondere die deutlich schnelleren Startzeiten. Sollen Microservices elastisch skaliert werden, ist das eine kritische Metrik. Die unter [3] gezeigten Statistiken sind beeindruckend und werden zweifelsohne für viele Anwendungen erhebliche Vorteile bringen.

Mein Hintergrund: Library-Autor

Ich arbeite beim Hersteller der gleichnamigen Graphdatenbank Neo4j Inc. Dort bin ich Teil des Treiber- beziehungsweise Spring-Data-Neo4j-Teams. Unsere Aufgabe ist die Bereitstellung von Datenbankkonnektoren für unterschiedliche Sprachen, inklusive Java. Zusammen mit Gerrit Meier (@meistermeier) entwickle und pflege ich Spring-Data-Neo4j.

Die Aufgabe des Java-Treibers ist erst einmal simpel: Herstellung einer Netzwerkverbindung zur Datenbank. Diese kann natürlich SSL-verschlüsselt betrieben werden. Darüber hinaus hat der Treiber einige Funktionen, die für die Arbeit mit einem Cluster aus Neo4j relevant sind.

Spring-Data-Neo4j und unser Object Mapping Framework setzen auf dem Treiber auf. Ihre Aufgabe ist es, quasi beliebige Domain-Modelle, die unsere Kunden und Nutzer in Form von annotierten Java-Klassen auf diese Frameworks werfen, in Cypher-Abfragen abzubilden beziehungsweise aus den Ergebnissen von Cypher-Abfragen zu materialisieren. Die Object Mapping Frameworks setzen dazu in der Regel Java Reflection ein. Mit Java Reflection werden benutzte Annotationen, Namen von Attributen etc. gelesen. Das ist natürlich etwas, das auch zur Kompilierungszeit möglich ist, aber die Entscheidung fiel an diesen Stellen ganz klar zugunsten der dynamischen Variante.

Im Zusammenhand mit den Schlagworten SSL und Java Reflection bin ich seit gut eineinhalb Jahren mit der GraalVM beschäftigt. Zum einen möchten wir Anwendungen, die unseren Java-Treiber verwenden, ermöglichen, nativ kompiliert zu werden, ohne auf SSL zu verzichten. Zum anderen möchten wir natürlich mit Version 6 von Spring-Data-Neo4j, einem kompletten Rewrite des Moduls, Teil des Spring Native Projects werden.

Auf diesem Standpunkt kann ich nicht nur großen Nutzen aus dem bestehenden Tooling um SubstrateVM ziehen, sondern auf der anderen Seite auch dazu beitragen, dass Menschen, die unseren Treiber nutzen oder Spring-Data-Neo4j nativ betreiben möchten, nicht selbst den Aufwand betreiben müssen, den wir und unsere Kollegen bei VMWare hinter sich haben, um Spring Data native zu kompilieren.

Frameworks

Neuere Microservices Frameworks wie Quarkus [4] und Micronaut [5] sind teilweise direkt unter dem Aspekt „Kompatiblität mit GraalVM Native“ entwickelt worden und bieten bereits seit 2019 dezidierte Hooks an, um die Erstellung von nativen Anwendungen zu vereinfachen.

In Hinblick auf Spring und Spring Boot existiert das aktuell als experimentell markierte Spring GraalVM Native Projekt [6]. Es bringt sowohl Annotationen und andere Hilfsmittel, die von Entwicklerinnen und Entwicklern für ihre Anwendungen genutzt werden können, als auch bereits vorgefertigte Pakete von Hinweisen, um Spring-Projekte selbst native lauffähig zu machen. Während ich Spring GraalVM Native im Java Magazin 9.20 nur andeutete [7], schrieb mein Freund Jonas Hecht einen exzellenten Artikel zum Thema Spring GraalVM Native [8].

Ich will heute aber gar nicht auf eins der konkreten Frameworks eingehen oder diskutieren, welcher Ansatz besser ist. Mir geht es im Folgenden darum, zu zeigen, was alles gemacht werden kann – nicht unbedingt und in jedem Fall muss –, um eine normale Java-Anwendung so zu ertüchtigen, dass sie mit GraalVM Native funktioniert.

Für die meisten Anwendungsentwicklerinnen und -entwickler wird sich die Frage „native oder nicht“ in einigen Jahren sicherlich nicht mehr mit der Wahl des Frameworks entscheiden, sondern wird unter fachlichen Gesichtspunkten beantwortet werden können. Damit das möglich ist, betreiben wir Library-Autor*innen und Framework-Entwickler*innen teils erheblichen Aufwand. Aufwand, der im besten Fall wohlwollend zur Kenntnis genommen wird, im schlechtesten Fall als „Magie“ verschrien ist. Letzteres möchte ich vermeiden. Es ist keine Magie, es ist teilweise Detektivarbeit, und notwendige Schritte können auch „zu Fuß“ nachvollzogen werden. Ich für meinen Teil schätze aber die kollaborative Arbeit, die in diesem Raum von den beteiligten Menschen geleistet wird.

Native Image

Seit meiner ersten Begegnung mit GraalVM während der JCrete 2017 ist ein ganzer Zoo an Werkzeugen entstanden. Eines dieser Werkzeuge heißt Native Image (Kasten: „Installation der GraalVM und des Native-Image-Tools“).

Installation der GraalVM und des Native-Image-Tools

Oracle stellt unter [9] sowohl die kostenfrei nutzbare Community-Edition als auch die lizenzpflichtige Enterprise-Version für mehrere Betriebssysteme zur Verfügung. Die Installation unterscheidet sich je nach Betriebssystem. Allen Varianten ist gemein, dass das Native-Image-Tool mit Hilfe des GraalVM Component Updater, kurz gu, nachinstalliert werden muss. Der Aufruf lautet – wenn GraalVM korrekt installiert wurde und das entsprechende bin-Verzeichnis im Pfad liegt – gu install native-image.

Unter Linux und macOS kann SDKMan! [10] genutzt werden, um GraalVM inklusive aller Tools zu installieren. Die ersten Schritte werden ausführlich unter [11] beschrieben.

Die vollständigen Quellen der folgenden Codeschnipsel stehen mit passender Verzeichnisstruktur auf meinem GitHub-Account unter [12] zur Verfügung.

Gegeben sei das triviale Java-Programm in Listing 1. Als Single-Source-File kann es mit jedem neuen JDK ohne Aufruf von javac gestartet werden. Ein java trivial/src/main/java/ac/simons/native_story/trivial/Application.java Michael produziert erwartungskonform Hello, Michael.

Listing 1: Triviales Java-Programm

package ac.simons.na...

Neugierig geworden?

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang