
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 gelesen und Tausende .class-Dateien geparst, geladen und gelinkt werden. Laden heißt: Die JVM bringt die Klasse aus der .class-Datei in ihre interne Datenstruktur. Linken heißt: Sie verifiziert die Klasse, bereitet ihre statischen Felder vor und löst ihre Verweise auf andere Klassen auf – erst danach kann die Klasse initialisiert 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 an einem kleinen Testprogramm 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 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 27. 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);
}
Kompiliere den Code:
javac AotTest.java
Erzeuge eine JAR-Datei:
jar cvf AotTest.jar AotTest.class
Starte dann den Trainingslauf mit folgendem Aufruf:
java -XX:AOTMode=record -XX:AOTConfiguration=AotTest.conf \
-cp AotTest.jar AotTest
Dadurch wird die Konfigurationsdatei AotTest.conf erzeugt. Auf Java 24 war das noch eine Textdatei mit der Liste aller geladenen Klassen, die du mit einem Texteditor lesen konntest; seit Java 25 ist es ein Binärformat, das nur die JVM selbst wieder einliest.
Danach erzeugst du mit folgendem Aufruf den AOT-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.jar
Und zuletzt startest du die Anwendung und gibst dabei die zu benutzende Cache-Datei an:
java -XX:AOTCache=AotTest.aot -cp AotTest.jar AotTest
Damit hast du alle drei Schritte gesehen. Die folgende Grafik stellt die beiden Wege zum Cache noch einmal gegenüber – die zwei einzelnen Kommandos und das kombinierte Kommando ab Java 25:
Lass uns nun einmal die Startzeit der Anwendung mit und ohne Cache vergleichen.
Zunächst ein Aufruf ohne Cache:
time java -cp AotTest.jar AotTest
Auf einem Apple M5 Pro mit Java 27 braucht dieser Aufruf im Median 45,0 Millisekunden.
Und jetzt ein Aufruf mit Cache:
time java -XX:AOTCache=AotTest.aot -cp AotTest.jar AotTest
Mit Cache sind es 34,7 Millisekunden – der Start ist 23 % schneller. Auf Java 24, 25 und 26 liegt die Ersparnis bei 22, 18 und 23 %.
Die 42 % aus JEP 483 erreicht das Beispiel damit nicht. Die JDK-Entwickler:innen haben ein anderes Programm gemessen, HelloStream, und zwei Java-Versionen verglichen: 31 Millisekunden auf Java 23 ohne Cache, 18 Millisekunden auf Java 24 mit Cache. Dasselbe Programm braucht auf dem M5 Pro 29,7 Millisekunden auf Java 23 und 21,8 Millisekunden auf Java 24 mit Cache – 26 % weniger. Wie viel der Cache bringt, hängt also auch vom System ab, auf dem die Anwendung startet.
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. Auf Java 24 konnte man das noch der Konfigurationsdatei ansehen: Ihr Kopf erklärte sie zur Klassenliste für 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 verkürzte sich die Startzeit der Anwendung um 33 %, 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.
Was der Cache seit CDS aufgenommen hat, zeigt die folgende Grafik Version für Version – bis zum nativen Code, der für Java 28 vorgeschlagen ist:
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. Mein Beispielprogramm startet auf Java 27 mit Cache 23 % schneller.
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. Dafür ist JEP 544: Ahead-of-Time Code Compilation für Java 28 eingeplant und bereits in die Codebasis von JDK 28 integriert. Laut JEP verkürzt der AOT-Cache den Start der getesteten Anwendungen auf einer Zwei-Kern-Maschine ohne AOT-Code um 50 bis 70 %, mit AOT-Code um 65 bis 80 %.
Was soll ich als Nächstes vertiefen? Das erfahre ich am besten durch dein Feedback: Eine Bewertung auf meinem ProvenExpert-Profil zeigt mir, welche Themen dir wichtig sind – und motiviert mich, weitere Artikel zu schreiben.




