🎯 OBJECTIF
Comprendre comment :
🧠 MODÈLE MENTAL
Historiquement, une appli Java prend des secondes à démarrer parce que la JVM doit charger des milliers de classes, les vérifier, et compiler le code chaud progressivement avec le JIT. Pour des conteneurs qu'on redémarre souvent, des fonctions serverless ou des CLIs, c'est rédhibitoire.
Depuis 2022-2023, l'écosystème a répondu en déplaçant du travail au build au lieu de le faire au runtime : pré-calcul de la config Spring, compilation native, snapshots de JVM chaude, archives de classes pré-linkées. Ces techniques ne s'excluent pas — elles opèrent à des niveaux différents (framework, JVM, compilateur) et se combinent.
Équation : moins de travail dynamique au runtime → démarrage plus rapide, mémoire réduite, parfois au prix de flexibilité perdue. Plus tu déplaces de travail au build, plus tu perds en flexibilité runtime. Le curseur va de "presque rien à perdre" (CDS/Leyden) à "renoncer au dynamique JVM" (GraalVM).
javac. Portable entre OS, exécuté par la JVM.flowchart TB
SA["Spring AOT engine\n(Spring Boot 3+)"]
SR["Spring Data AOT Repositories\n(Spring Data 2025.1)"]
GV["GraalVM native image"]
CR["CRaC"]
CDS["AppCDS / CDS"]
PL["Project Leyden\n(Java 24/25)"]
SA --> SR
SA -.utilisé par.-> GV
SA -.compatible avec.-> CR
SA -.compatible avec.-> CDSmermaidQuatre niveaux superposés :
Au démarrage, Spring Boot scanne toutes les classes annotées, résout les dépendances, crée les beans, évalue les conditions @ConditionalOn.... L'engine fait ce travail au build (mvn package) et génère du code Java qui contient déjà les bean definitions résolues.
Ce que ça inclut :
Activation :
# application.properties — Spring Boot 3+
spring.aot.enabled=truepropertiesmvn clean package
# Le code généré atterrit dans target/spring-aot/main/sources/bashAucun plugin supplémentaire requis — le plugin spring-boot-maven-plugin gère tout.
🚨 Quand ne pas l'utiliser
Application qui repose sur de la config dynamique au boot (recompositions de beans à chaud, post-processors custom) — l'AOT fige cette logique au build. Frameworks tiers non AOT-aware peuvent casser silencieusement sans erreur explicite.
Compile tout le code Java en exécutable natif autonome (plus de JVM, plus de JIT). Démarre directement comme un programme C. La réflexion non déclarée casse ce modèle — il faut des hints listant explicitement ce qui sera accédé par réflexion. Spring AOT génère ces hints automatiquement.
Activation :
<!-- pom.xml -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<extensions>true</extensions>
</plugin>xml# Requiert GraalVM JDK (sdk install java 21.0.5-graal)
mvn -Pnative native:compile
# Build 2-5 min → exécutable autonome dans target/
./target/mon-app # démarre en 20-50 msbash🚨 Quand ne pas l'utiliser
Appli longue durée de vie : le JIT HotSpot apprend des profils réels et finit par battre GraalVM après quelques minutes de warmup. Agents Java (JaCoCo, Datadog, profilers) : la plupart ne fonctionnent pas en native image — tu perds une partie de l'observabilité.
Démarrer l'appli une fois, la laisser chauffer (JIT optimise), puis prendre un snapshot de la JVM en mémoire : heap, stack, classes chargées, code JIT-compilé. En prod, restaurer ce snapshot en ~50 ms au lieu de redémarrer from scratch.
flowchart LR
subgraph CI["Build / CI"]
S1["Start JVM"] --> W1["Warmup\n(JIT chauffe)"]
W1 --> CP["Checkpoint\n(snapshot)"]
CP --> SN[("Fichiers snapshot")]
end
subgraph PROD["Prod"]
SN --> R1["Restore\n~50 ms"]
R1 --> READY["App ready\nJIT déjà chaud"]
endmermaidLes connexions TCP ne survivent pas — il faut les fermer avant le checkpoint et les rouvrir après. Spring Boot 3.2+ fait ça automatiquement pour les ressources standard (DataSource, Tomcat, Kafka clients).
Activation :
# JDK avec support CRaC (Azul Zulu CRaC, Liberica, Corretto 21+)
java -XX:CRaCCheckpointTo=./checkpoint-data -jar target/mon-app.jar
# Depuis un autre terminal, déclencher le snapshot
jcmd <PID> JDK.checkpoint
# En prod — restauration en ~50 ms
java -XX:CRaCRestoreFrom=./checkpoint-databash🚨 Risque de fuite de secrets
Le snapshot capture l'état complet du tas : secrets, tokens, données utilisateurs en mémoire au moment du checkpoint. Si le snapshot est stocké non chiffré ou transmis sans précaution, ces données sont exposées.
Au démarrage, la JVM reparse les mêmes milliers de classes à chaque fois. CDS fait ce travail une seule fois, sauvegarde le résultat dans un .jsa, et le mappe directement en mémoire au démarrage suivant. Gain typique : 20-40% de startup, sans toucher au code.
Technique la plus indolore du paysage — à activer par défaut.
# Méthode Spring Boot 3.3+
mvn clean package
# Génération du .jsa
java -Dspring.context.exit=onRefresh \
-XX:ArchiveClassesAtExit=app.jsa \
-jar target/mon-app.jar
# Utilisation en prod
java -XX:SharedArchiveFile=app.jsa -jar target/mon-app.jarbash# Via Cloud Native Buildpacks (automatisé)
mvn spring-boot:build-image \
-Dspring-boot.build-image.env.BP_JVM_CDS_ENABLED=truebashArchive invalidée au changement de classpath
Le .jsa est invalidé dès qu'une dépendance change. En prod c'est non-problème (redump au release). En dev local avec rebuild fréquent, c'est pénible — désactiver en local.
Extension de CDS qui pré-charge ET pré-linke les classes encore plus agressivement. Gain typique : 40-50% de startup en moins, juste avec un flag, sans changer de JVM ni perdre le dynamique HotSpot.
Disponible à partir de Java 24 (preview) / Java 25 (GA).
# Phase 1 — Entraînement (une exécution normale d'observation)
java -XX:AOTMode=record \
-XX:AOTConfiguration=app.aotconf \
-jar target/mon-app.jar
# Phase 2 — Création de l'archive AOT
java -XX:AOTMode=create \
-XX:AOTConfiguration=app.aotconf \
-XX:AOTCache=app.aot \
-jar target/mon-app.jar
# Phase 3 — Utilisation en prod
java -XX:AOTCache=app.aot -jar target/mon-app.jarbashJEP 514 (Java 25) simplifiera ce flux à un seul flag.
| Technique | Niveau | Gain startup | Gain mémoire | Coût adoption | Garde le JIT |
|---|---|---|---|---|---|
| Spring AOT engine | Framework | ~15-30% | ~5-10% | Faible | Oui |
| Spring Data AOT Repositories | Framework | marginal | marginal | Faible | Oui |
| CDS / AppCDS | JVM | ~20-40% | Nul | Très faible | Oui |
| Leyden (Java 24+) | JVM | ~40-50% | Faible | Très faible | Oui |
| CRaC | JVM + framework | ~95% (50 ms) | Faible | Moyen | Oui (chaud) |
| GraalVM native image | Compilation | ~99% (20-50 ms) | Énorme (÷5-10) | Élevé | Non |
Ces techniques ne s'excluent pas. Spring AOT est une fondation, les autres sont complémentaires.
⚡ TL;DR — chaque technique en une ligne
Spring AOT engine
✓ Pré-calcule au build les beans, conditions @ConditionalOn... et hints de réflexion — fondation de tout le reste, ~15-30% de startup en moins.
⚠ Config Spring figée au build — plus de bean dynamique calculé au boot, frameworks tiers non AOT-aware peuvent casser silencieusement.
Spring Data AOT Repositories
✓ Transforme les query methods en vraies classes Java compilées (*Impl__Aot), supprimant proxies réflexifs au runtime.
⚠ Le dialect SQL est gravé dans le code généré, aucun support du reactive (R2DBC ignoré), certaines query methods retombent en runtime sans warning.
CDS / AppCDS
✓ Cache les classes parsées dans un fichier .jsa mappé en mémoire partagée — gain 20-40%, juste deux flags JVM, technique la plus indolore.
⚠ L'archive .jsa est invalidée au moindre changement de classpath et mismatch JDK build/runtime invalide silencieusement l'archive.
Project Leyden (JEP 483) ✓ Extension de CDS qui pré-charge ET pré-linke encore plus agressivement — gain 40-50% de startup, sans toucher au code. ⚠ Uniquement Java 24 (preview) / Java 25 (GA), pas backporté sur LTS antérieures.
CRaC ✓ Snapshot d'une JVM déjà chaude via CRIU Linux, restauré en ~50 ms — garde tout le dynamique HotSpot, base d'AWS Lambda SnapStart. ⚠ Le snapshot capture l'état complet du tas (secrets, tokens en mémoire), connexions TCP ne survivent pas au restore, Linux-only.
GraalVM native image ✓ Compile tout en exécutable natif autonome — démarrage 20-50 ms, mémoire ÷5-10, ideal serverless. ⚠ Perd le JIT adaptatif (HotSpot le bat en stable state sur appli longue durée), perd la plupart des agents Java (JaCoCo, Datadog), build 2-5 min.
🎓 À retenir