JIT (Just-In-Time) est une technologie de compilation dynamique qui convertit le bytecode ou la représentation intermédiaire d'un programme en instructions machine directement pendant l'exécution. Dans Android, le compilateur JIT est apparu pour la première fois dans la version 2.2 Froyo au sein de la machine virtuelle Dalvik et a accéléré l'exécution des applications de 2 à 5 fois. Selon Google, 2024, le JIT moderne dans ART combine l'interprétation avec la compilation profilée des méthodes hot.
Points clés
Just-In-Time (JIT) est une méthode de compilation dans laquelle le code source ou le bytecode est converti en instructions machine non pas à l'avance (comme avec AOT), mais au moment du premier appel à la section correspondante du programme. Le terme « Just-In-Time » signifie que la compilation se produit « juste à temps » — immédiatement avant l'exécution.
Le concept de JIT existe depuis les années 1960, mais a été largement adopté avec l'avènement de la Machine Virtuelle Java en 1995. JIT permet de combiner la portabilité du bytecode (écrire une fois — exécuter partout) avec des performances proches du code natif. Dans la JVM HotSpot, le compilateur JIT analyse le code exécuté et ne compile que les sections les plus critiques, économisant temps et mémoire.
Le compilateur JIT reçoit le bytecode en entrée, l'interprète et collecte simultanément des statistiques. Lorsqu'une section de code (méthode, boucle) est appelée suffisamment souvent, JIT décide de la compiler. Le code machine compilé est stocké dans un cache — lors des appels suivants, la version déjà compilée est utilisée. Cela offre une accélération sans avoir à compiler l'intégralité du programme.
// Exemple : une méthode devient hot après plusieurs appels
public class HotMethod {
private int compute(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i * i;
}
return sum;
}
}
// Appel 500 fois dans une boucle — JIT compilera compute
for (int t = 0; t < 500; t++) {
hot.compute(1000);
}
Dans Android, la compilation JIT a traversé trois phases d'évolution. La première phase — Dalvik sans JIT (Android 1.0–2.1) : interprétation pure du bytecode DEX. La deuxième phase — Dalvik avec JIT (Android 2.2–4.4) : l'introduction du compilateur JIT, qui a accéléré les applications de 2 à 5 fois. La troisième phase — ART avec JIT hybride (Android 7.0+) : le retour de JIT dans une nouvelle capacité.
JIT dans Dalvik a été implémenté comme un compilateur basé sur les traces. Il analysait non pas des méthodes individuelles, mais des chaînes d'instructions (traces) qui sont fréquemment exécutées séquentiellement. Cela permettait de compiler des chemins d'exécution complets, incluant plusieurs méthodes. Cette approche était efficace pour les processeurs mobiles avec de petits caches d'instructions, car la trace compilée tenait dans le cache L1.
À partir d'Android 7.0 Nougat, ART utilise JIT basé sur les méthodes — il compile des méthodes individuelles en fonction des profils d'exécution. Ce JIT fonctionne nettement plus vite que Dalvik JIT : le temps de compilation typique d'une méthode est de 0,5–1 ms contre 3–5 ms dans Dalvik. Le code compilé est stocké dans une zone mémoire séparée (cache de code JIT) plutôt que dans le heap de l'application, ce qui réduit la fragmentation.
| Paramètre | Dalvik JIT | ART JIT |
|---|---|---|
| Type | Basé sur les traces | Basé sur les méthodes |
| Vitesse de compilation | 3–5 ms/méthode | 0,5–1 ms/méthode |
| Seuil de compilation | ~200 appels | Dynamique |
| Cache de code | Dans le heap de l'application | Cache de code JIT |
| Profilage | Interne | Fichiers .prof externes |
Le mécanisme central de JIT est la détection des méthodes hot. Chaque appel de méthode incrémente un compteur interne. Lorsque le compteur dépasse le seuil, la méthode est marquée comme « hot » et envoyée à la compilation. Dans Dalvik, le seuil était fixe (~200 appels). Dans ART, les compteurs sont configurés dynamiquement en fonction des ressources disponibles de l'appareil.
Le processus de compilation comprend plusieurs phases. La première — analyse du bytecode : JIT examine le flux d'instructions et construit un graphe de flux de données. La deuxième — optimisation : inlining des petites méthodes, élimination du code mort, pliage des constantes. La troisième — génération de code : conversion du graphe optimisé en instructions machine pour une architecture CPU spécifique (ARM, ARM64, x86).
// Démonstration d'inlining — JIT intégrera le corps de la méthode
public int inlineExample() {
return square(5);
}
private int square(int x) {
return x * x;
} // JIT remplacera l'appel par return 5 * 5;
Une technique spéciale de JIT — On-Stack Replacement (OSR). Si une méthode contient une longue boucle qui ne se termine pas pendant des centaines d'itérations, JIT peut compiler la boucle « à la volée » et remplacer la version interprétée par la version compilée directement pendant l'exécution. OSR est particulièrement efficace pour les tâches de calcul : rendu, traitement d'images, cryptographie.
JIT et AOT sont deux approches de compilation avec des compromis opposés. JIT sacrifie la vitesse du premier lancement pour une taille de distribution compacte et une adaptabilité. AOT sacrifie le temps d'installation et l'espace disque pour des performances maximales dès la première seconde. Aucune approche n'est absolument meilleure — le choix dépend du scénario.
L'avantage clé de JIT est l'optimisation adaptative. JIT peut utiliser des informations de profil indisponibles pour AOT : types d'objets exacts, fréquence d'appel réelle, motifs de branchement réels. Cela permet d'appliquer des optimisations agressives impossibles avec la compilation statique. Par exemple, JIT peut dévirtualiser les appels de méthode si un seul type de récepteur est rencontré en pratique.
| Critère | JIT | AOT |
|---|---|---|
| Temps d'installation | Instantané | Dépend de la taille |
| Premier lancement | Plus lent (préchauffage) | Rapide |
| Espace disque | Minimal | +15–30% |
| Adaptabilité | Élevée | Faible |
| Utilisation CPU | Pics pendant la compilation | Stable |
La compilation JIT est préférable lorsque le déploiement rapide et l'économie d'espace disque sont importants. Dans le contexte du développement mobile, JIT est idéal pour les applications qui sont mises à jour fréquemment (tests A/B, correctifs à chaud). JIT est également pratique pendant le développement, lorsque le code est reconstruit des dizaines de fois par jour — chaque seconde économisée sur la compilation accélère la boucle de rétroaction.
JIT offre aux développeurs une série d'avantages pratiques. Le premier — taille réduite de l'APK. Avec l'approche JIT, seul le bytecode (DEX) est empaqueté dans l'APK, qui prend 20–30% moins de place que le code natif compilé. Pour les utilisateurs avec un stockage interne limité, c'est un avantage significatif.
Le deuxième avantage est l'adaptation à l'appareil. JIT compile le code en tenant compte de l'architecture réelle du CPU, de la quantité de RAM et de la charge actuelle. Par exemple, sur un appareil avec 2 Go de RAM, JIT peut compiler moins agressivement pour économiser la mémoire, tandis que sur un flagship avec 12 Go, il peut appliquer toutes les optimisations possibles. La compilation AOT, en revanche, fixe la décision au moment de l'installation.
Le bytecode reste indépendant de la plateforme, ce qui simplifie la distribution des applications. Un seul APK fonctionne sur les appareils ARM, ARM64 et x86, et JIT génère du code natif pour chaque architecture. L'approche AOT nécessiterait soit d'inclure plusieurs variantes de code natif dans l'APK (augmentation de taille), soit de compiler une version séparée pour chaque architecture.
Le principal inconvénient de JIT est le délai de préchauffage. L'utilisateur subit des ralentissements dans les premières secondes de l'application pendant que JIT compile les méthodes hot. Dans les jeux, cela se manifeste par des bégaiements dans les niveaux initiaux. Dans les applications avec des animations — des transitions saccadées entre les écrans.
Le deuxième inconvénient — la consommation d'énergie. Le processus de compilation sollicite intensément le CPU, augmentant la consommation d'énergie de 10–20% pendant la période de préchauffage. Sur les appareils alimentés par batterie, cela réduit l'autonomie. C'est particulièrement notable dans les scénarios de redémarrages fréquents d'applications (multitâche avec mémoire limitée, où le système décharge et recharge les processus).
Un autre problème — la fragmentation du cache JIT. Le code compilé est stocké dans une zone mémoire continue. Lorsque de nouvelles classes sont chargées et que des méthodes supplémentaires sont compilées, le cache se fragmente, augmentant les frais de gestion de la mémoire. Dans Dalvik, ce problème était résolu par un nettoyage périodique du cache ; dans ART, le cache JIT est alloué séparément du heap et utilise sa propre stratégie de défragmentation.
L'approche moderne dans ART — la compilation hybride, combinant les forces de JIT et d'AOT. Pendant l'installation de l'application, aucune compilation n'est effectuée — seulement la vérification du bytecode (verify). Cela garantit une installation rapide et une utilisation minimale de l'espace. Les premiers lancements s'exécutent en mode interprétation avec compilation JIT des méthodes hot — l'utilisateur obtient des performances acceptables sans longs temps d'attente.
Parallèlement, un profileur en arrière-plan collecte des données sur l'utilisation réelle. Après 2–3 lancements complets de l'application, le profil atteint une complétude suffisante, et le système lance dex2oat pour compiler les méthodes hot en code natif. Cette opération est effectuée en arrière-plan lorsque l'appareil n'est pas sollicité (charge, écran éteint). Une fois l'AOT en arrière-plan terminée, l'application atteint des performances comparables à la compilation AOT complète.
# Démarrage forcé de la compilation en arrière-plan
adb shell cmd package compile -m speed-profile -f com.example.app
# Voir l'état de la compilation
adb shell cmd package dump-profiles com.example.app
Selon Google I/O 2017, la compilation hybride a réduit le temps d'installation des applications de 30–50% par rapport à l'AOT pur. L'espace disque occupé sur la partition système a diminué de 20–30%. Dans le même temps, les performances après la compilation en arrière-plan correspondent au niveau de l'AOT complet. Le seul scénario où l'hybride est inférieur à l'AOT est le premier lancement immédiatement après l'installation : l'application fonctionne en mode JIT et peut être 10–15% plus lente.
Questions fréquentes
JIT est un moyen d'accélérer un programme où le code est traduit en langage machine non pas à l'avance, mais par parties pendant l'exécution. Les sections les plus fréquentes sont compilées et mises en cache, tandis que les rares restent dans leur forme originale.
JIT compile le code pendant l'exécution, économisant de l'espace et accélérant l'installation. AOT compile tout le code à l'avance — l'application démarre plus vite mais nécessite plus d'espace disque et de temps d'installation.
JIT n'a pas été supprimé — il a évolué. Dans Android 5.0, Dalvik avec JIT a été remplacé par ART avec AOT pur. Dans Android 7.0, JIT est revenu dans ART dans le cadre d'un système hybride où il travaille avec la compilation AOT en arrière-plan pour des performances optimales.
JIT augmente la consommation d'énergie de 10–20% pendant la période de préchauffage en raison de la charge du CPU. Une fois la compilation des méthodes hot terminée, la consommation d'énergie revient à des niveaux normaux. Le mode hybride d'ART minimise ces pics grâce à la compilation en arrière-plan.
Oui, dans les scénarios avec des calculs intensifs. L'utilisateur peut remarquer des ralentissements dans les premières secondes de fonctionnement de l'application ou au début d'un jeu. Dans les versions modernes d'Android (8.0+), le mode hybride minimise cet effet grâce à la compilation profilée.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi