Individuelle JRE-Images mit dem JLinker erstellen

Mit dem neuen in Java 9 eingeführten Modulsystem lässt sich genau spezifizieren, welche Teile der Java Plattform API für ein spezielles Java Programm genau benötigt werden. Wäre es also möglich, für ein einzelnes Programm ein individuelles JRE zusammenzubauen, welches nur die erforderlichen Module für das Programm enthält?

Ja! Genau dafür ist der JLinker gedacht.

Der JLinker

Der JLinker (JEP 282) erlaubt eben diese Erstellung individueller Laufzeit-Images mit ausschließlich den notwendigen Plattform-API-Modulen und dem Basismodul java.base. Dieser ist Teil des JDK und wurde jetzt mit Java 9 im Umfang von JEP 275 offiziell veröffentlicht.

Bisher konnte etwa der Java Packager verwendet werden, um Binaries zu erstellen, welche dann das gesamte JRE enthalten. Diese Binaries sind dann aufgrund der Größe des JRE sehr groß geworden. Da seit Java 9 aber konkret spezifiziert werden kann, welche Teile der Java Laufzeitumgebung vom Programm benötigt werden, würde es genügen, ausschließlich diese in die Binary zu verpacken.

Diese dann viel kleinere Binary eignet sich hervorragend zum Einsatz auf Embedded-Geräten oder innerhalb von Containern, wie beispielsweise Docker, in einer Microservice-Umgebung.

Der Einsatz von JLinker ist in einer neuen, sogenannten “Link”-Phase vorgesehen. Diese Link-Phase befindet sich zwischen Compilezeit und Laufzeit. Nur hier können bestimmte Optimierungen günstig vorgenommen werden, die zur Compilezeit sehr schwierig und zur Laufzeit sehr teuer wären.

Der Aufruf des JLinker geschieht auf der Kommandozeile und sieht wie folgt aus:
jlink --module-path <Modulpfad> --add-modules <Module> --output <Ausgabepfad>

  • Der Parameter --module-path enthält dabei den Modulpfad der verwendeten Module, die (und natürlich deren transitive Abhängigkeiten) später im Image landen sollen.
  • --add-modules fügt weitere Module und deren transitive Abhängigkeiten zum Image hinzu.
  • Der Parameter --output spezifiziert den Ausgabepfad.

Weitere Argumente für den JLinker sind in der offiziellen Dokumentation von Oracle und im JEP 282 spezifiziert.

Auch ist die Verwendung von Plugins im Zusammenhang mit dem JLinker möglich. So lässt sich etwa das Image mit dem compress-Plugin mit unterschiedlichen Kompressionsstufen komprimieren. Auch möglich ist es, nur bestimmte Locales mit include-locales zu verwenden, oder mit exclude-files per regulärem Ausdruck bestimmte Dateien oder Dateitypen aus dem Image auszuschließen.

Vergleich Java Packager vs. JLinker in der Praxis

Für dieses Beispiel verwenden wir ein simples Hello-World-Beispiel.

Die dazugehörige Java Klasse wird im ersten Schritt kompiliert:
javac HelloWorld.java

Anschließend wird eine JAR-Datei generiert:
jar cvfe HelloWorld.jar de.oio.HelloWorld HelloWorld.class.

Schließlich wird mittels Java Packager und Inno Setup eine ausführbare exe-Datei mit allen benötigten Dateien generiert. Somit lässt sich das Hello-World-Programm auch ohne installierte JVM direkt aus dem Ordner heraus starten. Der Befehl zur Erstellung des Ordners lautet:
javapackager -deploy -native -outdir C:/Temp/HelloWorld -outfile sample -srcfiles HelloWorld.jar -appclass de.oio.HelloWorld -name "HelloWorld" -title "Hello World"

In C:\Temp\HelloWorld\bundles wurde nun ein Ordner namens HelloWorld generiert, der die Hello-World-Anwendung und die dazugehörige Java Runtime, sowie alle nötigen dll-Dateien enthält. Dieser Ordner ist stolze 192MB groß. Sehr respektabel für ein Programm, welches nur “Hello World” ausgibt und anschließend terminiert.

Als Vergleich dazu erstellen wir nun ein Image mit dem neuen JLinker durch den Befehl weiter oben. Heraus kommt eine Ordnerstruktur:
./
├── bin
├── conf
├── include
├── legal
└── lib

Im bin-Ordner findet sich eine java.exe. Unser Hello-World-Programm kann nun mit .\bin\java -m de.oio.main/de.oio.main.Main ausgeführt werden.

Wenn wir uns nun mit dem Befehl .\bin\java --list-modules die Module anzeigen lassen, lautet die Ausgabe:
de.oio.main
de.oio.util
java.base@10.0.1

Die gesamte Ordnerstruktur ist anschließend nur noch 36.9MB groß. Somit hat sich also eine Reduktion der Image-Größe um ca. 80% durch die Verwendung des JLinker ergeben.

Mit dem oben genannte Kompressions-Plugin lässt sich die Größe sogar noch weiter auf 24.9MB reduzieren, was weiteren 30% entspricht. Dafür muss an den Befehl oben bloß der Parameter --compress 2 angehängt werden.

Diese Ergebnisse sind in der folgenden Tabelle noch einmal zusammengefasst:

Image-Größen der Resultate unterschiedlicher Ansätze zur Image-Generation
Ansatz Größe Reduktion
Java Packager 192MB
Java Linker 36.9MB 80.78%
Java Linker mit Kompression (Stufe 2) 24.9MB 87.03%

Fazit

Letztendlich geht es also darum, auf Basis eines einzelnen Java-Programms ein individuelles und minimales JRE-Image erstellen zu können. Das ergibt Sinn, spart Platz und reduziert die Startup-Zeit, insbesondere in container-basierten Microservice-Umgebungen, in denen ein schlankes Deployment besonders wichtig ist. In unserem Test hat sich eine Größenreduktion des Laufzeit-Images durch den neuen JLinker um ca. 87% ergeben.

Short URL for this post: https://wp.me/p4nxik-32L
This entry was posted in Build, config and deploy and tagged , , , , . Bookmark the permalink.

Leave a Reply