In diesem Artikel erfährst du:
- Warum brauchen große Java-Anwendungen mehrere Sekunden, um zu starten?
- Was ist Ahead-of-Time Class Loading & Linking, und wie kann es die Startzeit verbessern?
- Schritt für Schritt: Wie kann man durch Ahead-of-Time Class Loading & Linking den Start einer Anwendung beschleunigen?
- Wie unterscheidet sich AoT Class Loading & Linking von (App)CDS?
Warum starten Java-Anwendungen so langsam?
Java-Anwendungen sind zur Laufzeit extrem flexibel, so können Klassen dynamisch geladen und entladen werden, dynamische Compilierung, Optimierung und Deoptimierung macht Java-Programme so schnell wie C-Code (oder schneller), und durch Reflection sind Frameworks wie Jakarta EE, Spring Boot, Quarkus, Helidon, Micronaut, etc. überhaupt erst möglich.
Doch diese Vorteile haben ihren Preis:
Beim Start einer Anwendung müssen Hunderte .jar-Dateien entpackt und Tausende .class-Dateien in den Speicher geladen, analysiert und verknüpft werden. Der statische Initialisierungscode von Klassen muss ausgeführt werden, und Frameworks wie Jakarta EE und Spring müssen den Code nach Annotationen scannen, Beans instanziieren und Konfigurationscode ausführen.
Große Backend-Anwendungen können so mehrere Sekunden oder sogar Minuten benötigen, bis sie vollständig gestartet sind.
Wie kann der Start von Anwendungen beschleunigt werden?
Viele der im vorherigen Abschnitt beschriebenen Initialisierungsarbeiten sind bei jedem Anwendungsstart dieselben.
Im Rahmen von Project Leyden wird daher daran gearbeitet, möglichst viele dieser sich immer wiederholenden Aufgaben bereits vor dem Start einer Anwendung auszuführen.
Durch JDK Enhancement Proposal 483 werden in Java 24 die ersten Früchte dieser Arbeit veröffentlicht: Klassen können nun nach dem Lesen, Parsen, Laden, und Linken in einer Binärdatei gecacht werden und sind damit bei zukünftigen Starts derselben Anwendung deutlich schneller in geladenem und gelinkten Zustand verfügbar.
Die Entwickler des Features haben Startzeit-Reduzierungen um bis zu 42 % gemessen.
Wie funktioniert Ahead-of-Time Class Loading & Linking?
Um den Programmstart durch AoT Class Loading & Linking zu beschleunigen, müssen wir drei Schritte durchführen:
- In einem ersten Schritt wird die Anwendung in einem sogenannten Trainingslauf gestartet. Dabei analysiert die JVM alle geladenen und gelinkten Klassen und erzeugt eine Konfigurationsdatei mit den relevanten Informationen über diese Klassen.
- In einem zweiten Schritt wird mit Hilfe dieser Konfigurationsdatei die binäre Cache-Datei erzeugt.
- Bei jedem weiteren Start der Anwendung wird diese Cache-Datei mit angegeben, und die Anwendung lädt die Klassen in geladener und gelinkter Form direkt aus diesem Cache.
Das hört sich komplizierter an als es ist. Im Folgenden führe ich dich Schritt für Schritt anhand einer Beispielanwendung durch diese Schritte bis hin zum beschleunigten Start der Anwendung.
Noch einmal Schritt für Schritt zum Mitmachen
Ich zeige dir in diesem Abschnitt an einer kleinen Anwendung Schritt für Schritt wie Ahead-of-Time Class Loading & Linking funktioniert.
Wir benutzen dazu ein ganz einfaches Demo-Programm, das lediglich die aktuelle Zeit anzeigt.
Wir verwenden hier eine einfache Klassendatei mit einer Instanz-Main-Methode. Dadurch müssen wir keine Klasse definieren und können statt public static void main(String args[])
einfach void main()
schreiben.
Lade dir ein Early-Access-Build von Java 24 herunter (Ahead-of-Time Class Loading & Linking ist erst ab Java 24 verfügbar).
Speichere den folgenden Quellcode in der Datei AotTest.java ab:
void main() {
var now = LocalDateTime.now();
var nowString = now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
System.out.println("Hello, it's " + nowString);
}
Code-Sprache: Java (java)
Kompiliere den Code (die Optionen --enable-preview --source 24
musst du angeben, da sich das o. g. Feature „Simple Source Files and Instance Main Methods“ noch im Preview-Stadium befindet):
javac --enable-preview --source 24 AotTest.java
Code-Sprache: Klartext (plaintext)
Erzeuge eine JAR-Datei:
jar cvf AotTest.jar AotTest.class
Code-Sprache: Klartext (plaintext)
Starte dann den Trainingslauf mit folgendem Aufruf:
java -XX:AOTMode=record -XX:AOTConfiguration=AotTest.conf \
--enable-preview -cp AotTest.jar AotTest
Code-Sprache: Klartext (plaintext)
Dadurch wird die Konfigurationsdatei AotTest.conf erzeugt. Diese kannst du mit einem Texteditor öffnen – sie enthält eine lange Liste von Klassen und Daten zu diesen Klassen.
Danach erzeugst du mit folgendem Aufruf den Klassen-Cache in der Datei AotTest.aot (die Anwendung wird hierbei nicht noch einmal ausgeführt):
java -XX:AOTMode=create -XX:AOTConfiguration=AotTest.conf -XX:AOTCache=AotTest.aot \
--enable-preview -cp AotTest.jar
Code-Sprache: Klartext (plaintext)
Und zuletzt startest du die Anwendung und gibst dabei die zu benutztende Cache-Datei an:
java -XX:AOTCache=AotTest.aot --enable-preview -cp AotTest.jar AotTest
Code-Sprache: Klartext (plaintext)
Lass uns nun einmal die Startzeit der Anwendung mit und ohne Cache vergleichen.
Zunächst ein Aufruf ohne Cache:
time java --enable-preview -cp AotTest.jar AotTest
Code-Sprache: Klartext (plaintext)
Bei fünf Aufrufen ergab sich bei mir eine mittlere Laufzeit von 0,137 Sekunden.
Und jetzt ein Aufruf mit Cache:
time java -XX:AOTCache=AotTest.aot --enable-preview -cp AotTest.jar AotTest
Code-Sprache: Klartext (plaintext)
Hier ergab sich aus fünf Aufrufen eine mittlere Laufzeit von 0,086 Sekunden. Das ist eine beeindruckende Leistungssteigerung von 37 %, die nah an die von den Entwicklern gemessenen 42 % herankommt.
Und was ist mit AppCDS?
Der aufmerksame Leser wird sich fragen: Was ist der Unterschied zwischen Ahead-of-Time Class Loading & Linking und (Application) Class Data Sharing?
Class Data Sharing (CDS) existiert bereits seit Java 5 und ermöglicht es, die Klassen des JDK in einem Plattform-spezifischen Binärformat zu speichern, aus dem die Klassen dann deutlich schneller geladen werden können als aus .class-Dateien.
In Java 10 kam dann Application Class Data Sharing (AppCDS) hinzu, wodurch nicht mehr nur JDK-Klassen, sondern auch Anwendungsklassen in diesem Binärformat gespeichert werden können.
Tatsächlich baut Ahead-of-Time Class Loading & Linking auf (App)CDS auf. Falls du dir vorhin die Datei AotTest.conf angeschaut hast, ist dir vielleicht aufgefallen, dass der Header sagt, es handle sich um ein „CDS archive dump“.
Während Class Data Sharing die Klassen lediglich liest, parst und dann in einem Binärformat speichert, werden die Klassen beim AoT Class Loading & Linking zusätzlich – wie der Name schon sagt – in Class
-Objekte geladen und gelinkt.
Die Leyden-Entwickler haben beide Mechanismen mit der Spring PetClinic getestet. Durch AppCDS wurde die Ladezeit der Anwendung um 33 % beschleunigt und durch AoT Class Loading & Linking um 42 %. Falls du also bereits AppCDS einsetzt, wird die Startzeitverbesserung durch AoT Class Loading & Linking nicht mehr ganz so signifikant ausfallen.
Fazit
Java ist eine sehr flexible und mächtige Sprache, doch diese Flexibilität kann bei größeren Anwendungen zu Startzeiten im Bereich von mehreren Sekunden bis Minuten führen. Beim Start werden u. a. Java-Klassen gelesen, geparst, geladen und gelinkt. Durch Ahead-of-Time Class Loading & Linking können diese Schritte vor dem Start der Anwendung einmalig ausgeführt werden und dadurch der eigentliche Start der Anwendung – nach Aussage der Entwickler dieses Features – um bis zu 42 % beschleunigt werden.
Falls du dieses Feature ausprobierst, schreib mir gerne deine Erfahrungen in die Kommentare – ich bin gespannt auf deine Messwerte und deine Meinung!
Möchtest du immer auf dem neuesten Stand bleiben und informiert werden, sobald ein neuer Artikel auf HappyCoders.eu veröffentlicht werden? Dann klicke hier, um dich für den HappyCoders.eu-Newsletter anzumelden.