

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. Klassen können dynamisch geladen und entladen werden. Dynamische Kompilierung, Optimierung und Deoptimierung machen Java-Programme so schnell wie C-Code – oder schneller. Und durch Reflection sind Frameworks wie Jakarta EE, Spring Boot, Quarkus, Helidon oder Micronaut ü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 wurden 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 – dem AOT-Cache – gespeichert werden und sind damit bei zukünftigen Starts derselben Anwendung deutlich schneller in geladenem und gelinktem Zustand verfügbar. Die JDK-Entwickler:innen haben Startzeit-Reduzierungen um bis zu 42 % gemessen.
Durch JDK Enhancement Proposal 514, Ahead-of-Time Command-Line Ergonomics, wurde in Java 25 die Generierung des AOT-Caches vereinfacht – statt zwei Schritten muss nur noch einer ausgeführt werden.
Durch JDK Enhancement Proposal 515, Ahead-of-Time Method Profiling, wurde – ebenfalls in Java 25 – der AOT-Cache um Statistiken über Methodenaufrufe erweitert, so dass die JVM die am häufigsten aufgerufenen Methoden („Hotspots“) schon kurz nach dem Start kompilieren kann, statt erst zur Laufzeit Profildaten sammeln zu müssen. Dadurch erreicht die Anwendung ihre Spitzenleistung schneller – gemessen wurde eine Reduzierung der Aufwärmzeit (Warmup) um 19 %.
Und in Java 26 kam mit JDK Enhancement Proposal 516, Ahead-of-Time Object Caching with Any GC, ein weiterer Baustein hinzu: Bis dahin wurden die vorinitialisierten Objekte des AOT-Caches in einem GC-spezifischen Format direkt in den Speicher gemappt – das funktionierte nicht mit jedem Garbage Collector. Seit Java 26 können die Objekte stattdessen in einem GC-neutralen Format sequenziell geladen werden. Dadurch lässt sich der AOT-Cache nun mit jedem Garbage Collector nutzen – einschließlich des Low-Latency-Collectors ZGC, der bisher außen vor war.
Wie funktioniert Ahead-of-Time Class Loading & Linking?
Um den Programmstart durch AOT Class Loading & Linking zu beschleunigen, müssen wir drei Schritte (bzw. zwei, dazu unten mehr) 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 – und ab Java 25 zusätzlich über Methoden-Aufrufstatistiken.
- 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 – und ab Java 25 beginnt sie direkt mit der Optimierung der am häufigsten aufgerufenen Methoden („Hotspots“).
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.
Lade dir ein aktuelles JDK herunter – idealerweise Java 25 (LTS) oder Java 26. Ahead-of-Time Class Loading & Linking ist seit Java 24 verfügbar; die in diesem Beispiel genutzten kompakten Quelldateien mit Instanz-Main-Methode sind seit Java 25 final, und die Option -XX:AOTCacheOutput, mit der wir die ersten beiden Schritte zu einem kombinieren können, steht ebenfalls ab Java 25 zur Verfügung.
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:
javac AotTest.javaCode-Sprache: Klartext (plaintext)Erzeuge eine JAR-Datei:
jar cvf AotTest.jar AotTest.classCode-Sprache: Klartext (plaintext)Starte dann den Trainingslauf mit folgendem Aufruf:
java -XX:AOTMode=record -XX:AOTConfiguration=AotTest.conf \
-cp AotTest.jar AotTestCode-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 \
-cp AotTest.jarCode-Sprache: Klartext (plaintext)Und zuletzt startest du die Anwendung und gibst dabei die zu benutzende Cache-Datei an:
java -XX:AOTCache=AotTest.aot -cp AotTest.jar AotTestCode-Sprache: Klartext (plaintext)Lass uns nun einmal die Startzeit der Anwendung mit und ohne Cache vergleichen.
Zunächst ein Aufruf ohne Cache:
time java -cp AotTest.jar AotTestCode-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 -cp AotTest.jar AotTestCode-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 Entwickler:innen gemessenen 42 % herankommt.
Und was ist mit AppCDS?
Du fragst dich jetzt vielleicht: 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 plattformspezifischen 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:innen 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:innen dieses Features – um bis zu 42 % beschleunigt werden.
Durch Ahead-of-Time Method Profiling werden zudem Statistiken über Methodenaufrufe im AOT-Cache gespeichert, so dass direkt nach Start der Anwendung häufig aufgerufene Methoden („Hotspots“) optimiert werden können. Und seit Java 26 funktioniert das Objekt-Caching des AOT-Caches mit jedem Garbage Collector – auch mit dem Low-Latency-Collector ZGC.
Damit ist das Ende der Entwicklung noch nicht erreicht: Als nächster Schritt zeichnet sich in Project Leyden die Ahead-of-Time-Code-Compilation ab – das Vorkompilieren häufig genutzter Methoden in nativen Code, der dann ebenfalls im AOT-Cache abgelegt wird. Bisher liegt dazu allerdings erst ein JEP draft: Ahead-of-Time Code Compilation vor, der noch keiner Java-Version zugeordnet ist.
Falls du dieses Feature ausprobierst, schreib mir gerne deine Erfahrungen in die Kommentare – ich bin gespannt auf deine Messwerte und deine Meinung!