Als ich anfing, mich genauer mit GraalVM zu beschäftigen, hatte ich nur eine grobe Vorstellung davon, was sich hinter der Bezeichnung eigentlich verbirgt. Beim Lesen der ersten Artikel zum Thema war ich geradezu verwirrt. Was ist GraalVM denn nun? Ein Ersatz für die HotSpot-JVM? Eine Möglichkeit, nativen Code aus Java zu erzeugen? Eine polyglotte VM? In diesem Artikel werde ich versuchen, einen Überblick über das GraalVM-Universum zu geben.
Das GraalVM-Projekt
Das GraalVM-Projekt von Oracle ist tatsächlich alles das, was ich in der Einleitung erwähnt habe. Dabei wird die Bezeichnung „GraalVM“ sowohl für das Gesamtprojekt als auch für Bestandteile des Projekts genutzt. Graal steht für „General Recursive Applicative and Algorithmic Language“. Neuerdings hat man sich bei Oracle offenbar darauf geeinigt, konsequent GraalVM statt Graal zu verwenden. Daher werde ich dies im Folgenden auch tun. Die Geschichte des GraalVM-Projekts geht zurück in die Zeit, als Java noch von Sun entwickelt wurde. In den folgenden Abschnitten werden ich die einzelnen Teile etwas genauer erläutern.
Grundlagen
Java-Quellcode wird zunächst in Bytecode übersetzt und kann dann von einer Java Virtual Machine (JVM) ausgeführt werden. Die JVM-Referenzimplementierung ist die HotSpot-JVM des OpenJDKs . Ohne ins Detail gehen zu wollen, kann die HotSpot-JVM entweder den Bytecode interpretieren oder ihn mit einem Compiler „just in time“ (JIT) in Maschinencode übersetzen. Die JIT-Compiler in der HotSpot-JVM C1 und C2 sind sehr alt, schwer wartbar und in C++ sowie Assembler geschrieben.
GraalVM-Compiler
Das Fundament für das GraalVM-Projekt wurde mit der Maxine VM gelegt. Eine Grundlage dieses Forschungsprojekts war es, im Gegensatz zur HotSpot-JVM möglichst sämtlichen Code in Java zu schreiben und nicht in C++. Daraus entstanden ist der GraalVM-Compiler. Dieser kann in zwei Modi verwendet werden: Als Just-in-Time-(JIT-)Compiler als Ersatz für den betagten C2 und als Ahead-of-Time-(AOT-)Compiler in Kombination mit der Substrate VM für die GraalVM-Native-Image-Funktion.
GraalVM-Compiler als JIT-Compiler
Mit Java 8 wurde das JVM Compiler Interface (JVMCI) eingeführt, um alternative JIT-Compiler in die JVM einbinden zu können. Der GraalVM-Compiler nutzt dieses Interface und bietet dabei viele Vorteile gegenüber den bestehenden JIT-Compilern. Er kann besser optimierten Maschinencode produzieren, was insbesondere Scala-Anwendungen, aber auch Java-Anwendungen beschleunigt. Da der Compiler in Java geschrieben ist, muss man bei der Entwicklung nicht mehr zwischen zwei verschiedenen Welten (C++ und Java) springen. Außerdem kann der Compiler sich selbst optimieren, da er auch nur eine Java-Anwendung ist. Letzteres hat den Nachteil, dass das „Aufwärmen“ einer Anwendung ggf. länger dauern kann, da der GraalVM-Compiler sich erst selbst optimieren muss, bis er eine hohe Leistung erreicht. Aus diesem Grund wurde mit Hilfe der GraalVM Native Image-Funktion eine vorkompilierte Version des Compilers namens „libgraal“ geschaffen. Um die Vorteile des GraalVM-Compilers nutzen zu können, muss man sich nur die GraalVM-Distribution herunterladen und anstelle der HotSpot-JVM einsetzen.
GraalVM Native Image
Durch den Einsatz des GraalVM-Compilers als JIT-Compiler kann die maximale Leistung von JVM-Anwendungen erhöht werden. Das ist insbesondere für langlaufende Serveranwendungen gut. Kurzlebige Anwendungen leiden dagegen unter der langen Startzeit und der hohen Speicherauslastung der JVM. Hier setzt die GraalVM-Native-Image-Funktion an. Mithilfe des GraalVM-Compilers wird dabei vor der Ausführung der Anwendung („ahead of time“) eine nativ ausführbare Datei erstellt, ein sogenanntes „Native Image“. Da zur Laufzeit trotzdem Komponenten wie Garbage Collector und Thread Scheduler benötigt werden, wird die sogenannte Substrate VM eingebunden, die diese Laufzeitkomponenten bereitstellt. Native Images haben im Vergleich zu einer JVM deutlich kürzere Startzeiten und niedrigeren Speicherverbrauch. Dafür ist die maximale Leistung (noch) nicht so hoch, da zugunsten der kürzeren Startzeiten auf einige adaptive Laufzeitoptimierungen verzichtet wird. Außerdem gibt es Einschränkungen bei der Entwicklung von Anwendungen, die zu einem Native Image kompiliert werden sollen, z.B. bei der Verwendung von Reflection.
Polyglotte VM
Neben dem GraalVM-Compiler und der Native Image-Funktionalität bietet sich GraalVM mithilfe des Truffle-Frameworks außerdem als Laufzeitumgebung für interpretierte Sprachen an. Truffle ermöglicht es, aus einem Interpreter einen hoch-performanten Compiler zu generieren. Aktuell ist es möglich, Anwendungen für Node.js, Ruby, R und Python mit der GraalVM auszuführen. Diese können dabei von allen Möglichkeiten der GraalVM profitieren und laufen dadurch teilweise deutlich schneller, als im jeweils besten verfügbaren Interpreter. Des Weiteren lässt sich mit Hilfe von Sulong LLVM-Bitcode in der GraalVM ausführen. Dadurch lassen sich u. a. Programme starten, die in C oder Rust geschrieben wurden. Doch damit nicht genug, bietet GraalVM außerdem die Möglichkeit, verschiedene Sprachen in einer Anwendung zu mischen. Alle Sprachen laufen dabei im gleichen Kontext und so kann man z.B. aus einer JavaScript-Anwendung heraus ohne Overhead direkt Bibliotheken in R oder Python für Data Science oder das Zeichnen von Graphen verwenden.
Community und Enterprise Edition
GraalVM wird in einer Community und einer Enterprise Edition vertrieben. Die Community Edition lässt sich kostenlos auch in Produktion nutzen. Die Enterprise Edition bietet u. a. weitergehende Optimierungen des GraalVM-Compilers. Zu Test- oder Demozwecken lässt sie sich kostenlos nutzen, lediglich für den Betrieb in Produktion wird eine kostenpflichtige Lizenz benötigt.
Aktueller Stand und Ausblick
Die GraalVM mit dem GraalVM-Compiler als Ersatz für die HotSpot-JVM ist bereits produktionsreif und wird u. a. erfolgreich von Oracle Cloud Infrastructure Monitoring benutzt. Windows wird bisher allerdings nur experimentell unterstützt.
Die Native-Image-Funktion ist als „Early Adopter Technology “ gekennzeichnet. Frameworks wie Quarkus , Micronaut und Helidon unterstützen sie aber schon. Spring arbeitet an der Unterstützung und will diese mit Spring 5.3 ausliefern.
Die Unterstützung von Node.js ist offenbar ausgereift, dagegen wird die Unterstützung von Ruby, R und Python noch als experimentell bezeichnet.
Man kann also schon jetzt vom besseren JIT-Compiler profitieren und damit Ressourcen sparen. Durch die Native-Image-Funktion haben Java-Anwendungen das Potential, in Bereiche vorzudringen, in denen Java traditionell Schwächen hat. Durch den geringen Ressourcenverbrauch und die kurzen Startzeiten werden z. B. Ansätze wie Serverless/FaaS auch für Java-Entwickler interessant. Die kommende Unterstützung durch Spring wird sicherlich dazu beitragen. Nicht zuletzt sind die Möglichkeiten durch die Benutzung verschiedener Sprachen riesengroß.
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Timo
Senior Software Engineer
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.