JIT (Just-In-Time) ist eine dynamische Kompilierungstechnologie, die Bytecode oder eine Zwischendarstellung eines Programms direkt während der Ausführung in Maschinenanweisungen umwandelt. In Android erschien der JIT-Compiler erstmals in Version 2.2 Froyo als Teil der virtuellen Maschine Dalvik und beschleunigte die Ausführung von Anwendungen um das 2–5-fache. Laut Google, 2024 kombiniert der moderne JIT in ART die Interpretation mit der profilierten Kompilierung von heißen Methoden.
Wichtige Erkenntnisse
Just-In-Time (JIT) ist eine Kompilierungsmethode, bei der Quellcode oder Bytecode nicht im Voraus (wie bei AOT), sondern zum Zeitpunkt des ersten Aufrufs des entsprechenden Programmabschnitts in Maschinenanweisungen umgewandelt wird. Der Begriff „Just-In-Time“ bedeutet, dass die Kompilierung „gerade rechtzeitig“ erfolgt — unmittelbar vor der Ausführung.
Das Konzept von JIT existiert seit den 1960er Jahren, fand jedoch mit dem Aufkommen der Java Virtual Machine im Jahr 1995 breite Akzeptanz. JIT ermöglicht die Kombination der Portabilität von Bytecode (einmal schreiben — überall ausführen) mit einer Leistung nahe an nativem Code. In der Java HotSpot VM analysiert der JIT-Compiler den ausgeführten Code und kompiliert nur die kritischsten Abschnitte, wodurch Zeit und Speicher gespart werden.
Der JIT-Compiler erhält Bytecode als Eingabe, interpretiert ihn und sammelt gleichzeitig Statistiken. Wenn ein bestimmter Codeabschnitt (Methode, Schleife) häufig genug aufgerufen wird, entscheidet JIT, ihn zu kompilieren. Der kompilierte Maschinencode wird in einem Cache gespeichert — bei nachfolgenden Aufrufen wird die bereits kompilierte Version verwendet. Dies bietet eine Beschleunigung, ohne das gesamte Programm kompilieren zu müssen.
// Beispiel: Eine Methode wird nach mehreren Aufrufen heiß
public class HotMethod {
private int compute(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i * i;
}
return sum;
}
}
// 500-mal in einer Schleife aufrufen — JIT wird compute kompilieren
for (int t = 0; t < 500; t++) {
hot.compute(1000);
}
In Android durchlief die JIT-Kompilierung drei Evolutionsphasen. Die erste Phase — Dalvik ohne JIT (Android 1.0–2.1): reine Interpretation von DEX-Bytecode. Die zweite Phase — Dalvik mit JIT (Android 2.2–4.4): Einführung des JIT-Compilers, der Anwendungen um das 2–5-fache beschleunigte. Die dritte Phase — ART mit hybridem JIT (Android 7.0+): die Rückkehr von JIT in einer neuen Funktion.
JIT in Dalvik wurde als trace-basierter Compiler implementiert. Er analysierte nicht einzelne Methoden, sondern Befehlsketten (Traces), die häufig sequenziell ausgeführt werden. Dies ermöglichte die Kompilierung vollständiger Ausführungspfade einschließlich mehrerer Methoden. Dieser Ansatz war effektiv für mobile Prozessoren mit kleinen Befehlscaches, da der kompilierte Trace in den L1-Cache passte.
Ab Android 7.0 Nougat verwendet ART methodenbasiertes JIT — es kompiliert einzelne Methoden basierend auf Ausführungsprofilen. Dieses JIT arbeitet deutlich schneller als Dalvik JIT: Die typische Kompilierungszeit für eine Methode beträgt 0.5–1 ms gegenüber 3–5 ms in Dalvik. Der kompilierte Code wird in einem separaten Speicherbereich (JIT-Code-Cache) und nicht im Anwendungs-Heap gespeichert, was die Fragmentierung reduziert.
| Parameter | Dalvik JIT | ART JIT |
|---|---|---|
| Typ | Trace-basiert | Methodenbasiert |
| Kompilierungsgeschwindigkeit | 3–5 ms/Methode | 0.5–1 ms/Methode |
| Kompilierungsschwelle | ~200 Aufrufe | Dynamisch |
| Code-Cache | Im Anwendungs-Heap | JIT-Code-Cache |
| Profilierung | Intern | Externe .prof-Dateien |
Der zentrale Mechanismus von JIT ist die Erkennung heißer Methoden. Jeder Methodenaufruf erhöht einen internen Zähler. Wenn der Zähler die Schwelle überschreitet, wird die Methode als „heiß“ markiert und zur Kompilierung gesendet. In Dalvik war die Schwelle festgelegt (~200 Aufrufe). In ART werden die Zähler dynamisch in Abhängigkeit von den verfügbaren Ressourcen des Geräts konfiguriert.
Der Kompilierungsprozess umfasst mehrere Phasen. Die erste — Bytecode-Analyse: JIT untersucht den Befehlsstrom und erstellt einen Datenflussgraphen. Die zweite — Optimierung: Inlining kleiner Methoden, Entfernung von totem Code, Konstantenfaltung. Die dritte — Codegenerierung: Umwandlung des optimierten Graphen in Maschinenanweisungen für eine bestimmte CPU-Architektur (ARM, ARM64, x86).
// Demonstration von Inlining — JIT wird den Methodenrumpf einbetten
public int inlineExample() {
return square(5);
}
private int square(int x) {
return x * x;
} // JIT wird den Aufruf durch return 5 * 5; ersetzen
Eine spezielle JIT-Technik — On-Stack Replacement (OSR). Wenn eine Methode eine lange Schleife enthält, die über Hunderte von Iterationen nicht endet, kann JIT die Schleife „im laufenden Betrieb“ kompilieren und die interpretierte Version direkt während der Ausführung durch die kompilierte ersetzen. OSR ist besonders effektiv für rechenintensive Aufgaben: Rendering, Bildverarbeitung, Kryptographie.
JIT und AOT sind zwei Kompilierungsansätze mit gegensätzlichen Kompromissen. JIT opfert die Geschwindigkeit des ersten Starts für eine kompakte Distributionsgröße und Anpassungsfähigkeit. AOT opfert Installationszeit und Speicherplatz für maximale Leistung von der ersten Sekunde an. Keiner der Ansätze ist absolut besser — die Wahl hängt vom Szenario ab.
Der Hauptvorteil von JIT ist die adaptive Optimierung. JIT kann Profilinformationen nutzen, die AOT nicht zur Verfügung stehen: genaue Objekttypen, tatsächliche Aufruffrequenz, tatsächliche Verzweigungsmuster. Dies ermöglicht aggressive Optimierungen, die bei statischer Kompilierung unmöglich sind. Beispielsweise kann JIT Methodenaufrufe devirtualisieren, wenn in der Praxis nur ein Empfängertyp vorkommt.
| Kriterium | JIT | AOT |
|---|---|---|
| Installationszeit | Sofort | Abhängig von der Größe |
| Erster Start | Langsamer (Aufwärmen) | Schnell |
| Speicherplatz | Minimal | +15–30% |
| Anpassungsfähigkeit | Hoch | Niedrig |
| CPU-Auslastung | Spitzen bei Kompilierung | Stabil |
JIT-Kompilierung ist vorzuziehen, wenn eine schnelle Bereitstellung und Speicherplatzersparnis wichtig sind. Im Kontext der mobilen Entwicklung ist JIT ideal für Anwendungen, die häufig aktualisiert werden (A/B-Tests, Hotfixes). JIT ist auch während der Entwicklung praktisch, wenn Code dutzende Male am Tag neu erstellt wird — jede bei der Kompilierung eingesparte Sekunde beschleunigt die Rückkopplungsschleife.
JIT bietet Entwicklern eine Reihe praktischer Vorteile. Der erste — kleine APK-Größe. Beim JIT-Ansatz wird nur Bytecode (DEX) in die APK verpackt, der 20–30% weniger Platz benötigt als kompilierter nativer Code. Für Benutzer mit begrenztem internen Speicher ist dies ein bedeutender Vorteil.
Der zweite Vorteil ist die Geräteanpassung. JIT kompiliert Code unter Berücksichtigung der tatsächlichen CPU-Architektur, der RAM-Größe und der aktuellen Last. Beispielsweise kann JIT auf einem Gerät mit 2 GB RAM weniger aggressiv kompilieren, um Speicher zu sparen, während es auf einem Flaggschiff mit 12 GB alle möglichen Optimierungen anwenden kann. Die AOT-Kompilierung hingegen fixiert die Entscheidung zum Zeitpunkt der Installation.
Bytecode bleibt plattformunabhängig, was die Verteilung von Anwendungen vereinfacht. Eine APK funktioniert auf ARM-, ARM64- und x86-Geräten, und JIT generiert nativen Code für jede Architektur. Der AOT-Ansatz würde entweder das Einfügen mehrerer nativer Code-Varianten in die APK (Größensteigerung) oder das Kompilieren einer separaten Version für jede Architektur erfordern.
Der Hauptnachteil von JIT ist die Aufwärmverzögerung. Der Benutzer erlebt Verlangsamungen in den ersten Sekunden der Anwendung, während JIT heiße Methoden kompiliert. In Spielen äußert sich dies als Ruckeln in den Anfangsleveln. In Anwendungen mit Animationen — ruckartige erste Übergänge zwischen Bildschirmen.
Der zweite Nachteil — Energieverbrauch. Der Kompilierungsprozess belastet die CPU stark und erhöht den Energieverbrauch während der Aufwärmphase um 10–20%. Bei batteriebetriebenen Geräten verkürzt dies die Akkulaufzeit. Besonders bemerkbar ist dies in Szenarien mit häufigen Anwendungsneustarts (Multitasking mit begrenztem Speicher, bei dem das System Prozesse entlädt und neu lädt).
Ein weiteres Problem — die JIT-Cache-Fragmentierung. Der kompilierte Code wird in einem kontinuierlichen Speicherbereich gespeichert. Wenn neue Klassen geladen und zusätzliche Methoden kompiliert werden, fragmentiert der Cache, was den Speicherverwaltungsaufwand erhöht. In Dalvik wurde dieses Problem durch regelmäßiges Cache-Leeren gelöst; in ART wird der JIT-Cache getrennt vom Heap zugewiesen und verwendet eine eigene Defragmentierungsstrategie.
Der moderne Ansatz in ART — die hybride Kompilierung, die die Stärken von JIT und AOT vereint. Während der Installation der Anwendung wird keine Kompilierung durchgeführt — nur die Bytecode-Überprüfung (Verify). Dies gewährleistet eine schnelle Installation und minimale Speichernutzung. Die ersten Starts erfolgen im Interpretationsmodus mit JIT-Kompilierung heißer Methoden — der Benutzer erhält eine akzeptable Leistung ohne lange Wartezeiten.
Parallel dazu sammelt ein Hintergrundprofiler Daten über die tatsächliche Nutzung. Nach 2–3 vollständigen Anwendungsstarts erreicht das Profil ausreichende Vollständigkeit, und das System startet dex2oat, um heiße Methoden in nativen Code zu kompilieren. Diese Operation wird im Hintergrund ausgeführt, wenn das Gerät nicht ausgelastet ist (Laden, Bildschirm aus). Nach Abschluss der Hintergrund-AOT erreicht die Anwendung eine Leistung, die mit der vollständigen AOT-Kompilierung vergleichbar ist.
# Erzwungener Start der Hintergrundkompilierung
adb shell cmd package compile -m speed-profile -f com.example.app
# Kompilierungsstatus anzeigen
adb shell cmd package dump-profiles com.example.app
Laut Google I/O 2017 reduzierte die hybride Kompilierung die Installationszeit von Anwendungen um 30–50% im Vergleich zu reinem AOT. Der belegte Speicherplatz auf der Systempartition verringerte sich um 20–30%. Gleichzeitig entspricht die Leistung nach der Hintergrundkompilierung dem Niveau der vollständigen AOT. Das einzige Szenario, in dem der Hybrid AOT unterlegen ist, ist der erste Start unmittelbar nach der Installation: Die Anwendung läuft im JIT-Modus und kann 10–15% langsamer sein.
Häufig gestellte Fragen
JIT ist eine Möglichkeit, ein Programm zu beschleunigen, bei der Code nicht im Voraus, sondern während der Laufzeit in Teilen in Maschinensprache übersetzt wird. Die häufigsten Abschnitte werden kompiliert und zwischengespeichert, während seltene in ihrer ursprünglichen Form bleiben.
JIT kompiliert Code während der Ausführung, spart Speicherplatz und beschleunigt die Installation. AOT kompiliert den gesamten Code im Voraus — die Anwendung startet schneller, benötigt aber mehr Speicherplatz und Installationszeit.
JIT wurde nicht entfernt, sondern weiterentwickelt. In Android 5.0 wurde Dalvik mit JIT durch ART mit reinem AOT ersetzt. In Android 7.0 kehrte JIT als Teil eines hybriden Systems zu ART zurück, wo es für optimale Leistung mit der Hintergrund-AOT-Kompilierung zusammenarbeitet.
JIT erhöht den Energieverbrauch während der Aufwärmphase aufgrund der CPU-Last um 10–20%. Nach Abschluss der Kompilierung heißer Methoden normalisiert sich der Energieverbrauch wieder. Der Hybridmodus von ART minimiert diese Spitzen durch Hintergrundkompilierung.
Ja, in Szenarien mit intensiven Berechnungen. Der Benutzer kann in den ersten Sekunden des Anwendungsbetriebs oder zu Beginn eines Spiels Verlangsamungen bemerken. In modernen Android-Versionen (8.0+) minimiert der Hybridmodus diesen Effekt dank der profilierten Kompilierung.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch