Runtimes
GraalVM Native Image vs JVM + cache AOT — démarrage à froid d'un binaire, débit de la JVM. Choisis l'un, ou livre les deux.
La plupart des frameworks desktop forcent un compromis : boot instantané au prix du débit, ou débit maximal au prix d'un démarrage lent. Nucleus laisse choisir — ou livrer les deux et laisser l'utilisateur trancher.
L'essentiel
- GraalVM Native Image — compilation AOT en monde fermé. Démarrage en ~50 ms, ~60 Mo de RAM, binaire autonome de ~38 Mo. Pas de JRE embarquée.
- JVM + cache AOT — le cache CDS / AOT du JDK 25 pré-chauffe les classes et profils. Démarrage en ~1 s, JIT HotSpot pour le débit max sur les hot paths.
- Même DSL, un seul switch. Une ligne Gradle pour basculer.
Démarrage à froid d'un binaire
nucleus.application {
graalvm {
isEnabled.set(true)
javaLanguageVersion.set(25)
}
}Ce qu'on gagne :
- Instantané. Vraiment instantané. ~50 ms entre
./MyAppet la première frame — la même sensation qu'ouvrir un fichier de config. - Pas de JRE à embarquer. Un binaire autonome par OS. Installateur minimal, aucune surprise Java pour les utilisateurs.
- Nucleus résout la reflection automatiquement. Tous les
reflect-config,resource-configet métadonnées JNI nécessaires pour chaque module Nucleus — auto-injectés. Zéro config manuelle.
Le compromis : monde fermé. Pas de chargement dynamique de classes, pas d'agents, pas de JFR, pas de ScriptEngine dynamique. Si le app est une surface produit stable, c'est OK — c'est le cas de la plupart des apps desktop. Si on est un host de plugins ou un outil de dev, regarde l'option suivante.
Débit de la JVM
nucleus.application {
nativeDistributions {
enableAotCache = true
}
}Ce qu'on gagne :
- HotSpot, le JIT le plus tuné au monde. Des décennies d'escape analysis, d'inlining, de vectorisation. Les hot paths frôlent le C++ et le Rust à la main.
- Le cache AOT règle le démarrage à froid. Le CDS / AOT du JDK 25 pré-chauffe classes, profils et code — démarrage en ~1 s avec un JIT déjà tiered, prêt à compiler.
- Pas de contraintes monde fermé. Reflection, chargement dynamique, agents, JFR,
ScriptEngine— tout est dispo. Idéal pour les hosts de plugins, l'outillage IDE, tout ce qui est dynamique.
Le compromis : on embarque une JRE. Le cache AOT ajoute quelques Mo, mais le runtime est le HotSpot complet.
Chiffres côte à côte
| Métrique | GraalVM Native Image | JVM + cache AOT |
|---|---|---|
| Démarrage à froid | ~50 ms | ~1 s |
| Débit CPU max | ~85 % (AOT compilé, pas d'escalade JIT) | ~98 % (JIT tiered, frôle C++/Rust) |
| RAM au repos | ~60 Mo | ~180 Mo |
| Taille du binaire | ~38 Mo (autonome) | ~55 Mo (cache AOT inclus) |
| Fonctionnalités dynamiques | Monde fermé | Complet |
Chiffres pour un Hello World macOS Apple Silicon — le kilométrage variera, mais la forme tient.
Même DSL, un seul switch
nucleus.application {
runtime = if (isReleaseBuild) Runtime.NativeImage else Runtime.JvmWithAotCache
}[FACT-CHECK NEEDED] Le raccourci
runtime = …apparaît dans le CodeSection du landing mais ne figure pas dans l'inventaire actuel duplugin-build— le vrai switch en 2.0.0 c'estgraalvm.isEnabled.set(true)etnativeDistributions.enableAotCache. Considère la ligne ci-dessus comme la forme visée ; en attendant, utilise les deux switches réels.
En attendant, les deux switches réels équivalents :
nucleus.application {
nativeDistributions { enableAotCache = true }
graalvm { isEnabled.set(false) } // JVM + cache AOT
}nucleus.application {
nativeDistributions { enableAotCache = false }
graalvm { isEnabled.set(true) } // GraalVM Native Image
}Détecter le runtime au runtime
Utilise aot-runtime :
import dev.nucleusframework.aotruntime.AotRuntime
import dev.nucleusframework.aotruntime.AotRuntimeMode
import dev.nucleusframework.core.runtime.ExecutableRuntime
import dev.nucleusframework.core.runtime.ExecutableType
when (AotRuntime.mode()) {
AotRuntimeMode.TRAINING -> { /* skip le boulot pendant la passe d'entraînement */ }
AotRuntimeMode.RUNTIME -> { /* lancement visible par l'utilisateur */ }
AotRuntimeMode.NONE -> { /* JVM standard ou binaire GraalVM */ }
}
// Dans quel type de binaire je tourne ?
val type: ExecutableType = ExecutableRuntime.type() // Jar, AppImage, Dmg, Exe, Msi, AppX, …Dans nucleusApplication { … }, le scope expose aotMode, isAotTraining et isAotRuntime directement.
Et ensuite ?
- GraalVM — métadonnées de reachability, build args, intégration CI.
- AOT cache — passes d'entraînement, lifecycle, ce qui est caché.
- Configuration — le DSL Gradle complet.
Architecture
Les quatre couches sous le main() — Nucleus, Compose Multiplatform, Kotlin Multiplatform et le runtime JDK/GraalVM — et comment chacune fait son travail.
Backends
Deux backends de fenêtre derrière le même DecoratedWindow — AWT (legacy, JBR) ou Tao (la nouvelle valeur par défaut en 2.0, fenêtrage natif Rust).