JIT (Just-In-Time) — technologia dynamicznej kompilacji, przekształcająca kod bajtowy lub pośrednią reprezentację programu w instrukcje maszynowe bezpośrednio podczas wykonywania. W Androidzie kompilator JIT pojawił się po raz pierwszy w wersji 2.2 Froyo w ramach maszyny wirtualnej Dalvik i przyspieszył działanie aplikacji 2–5 razy. Według danych Google, 2024, nowoczesny JIT w ART łączy interpretację z profilowaną kompilacją hot-metod.
Najważniejsze
Just-In-Time (JIT) — metoda kompilacji, w której kod źródłowy lub kod bajtowy jest przekształcany na instrukcje maszynowe nie z wyprzedzeniem (jak w AOT), ale w momencie pierwszego wywołania odpowiedniego fragmentu programu. Termin „Just-In-Time” oznacza, że kompilacja odbywa się „akurat w porę” — bezpośrednio przed wykonaniem.
Koncepcja JIT istnieje od lat 60. XX wieku, ale szerokie rozpowszechnienie zyskała wraz z pojawieniem się Java Virtual Machine w 1995 roku. JIT pozwala łączyć przenośność kodu bajtowego (piszemy raz — uruchamiamy wszędzie) z wydajnością zbliżoną do kodu natywnego. W Java HotSpot VM kompilator JIT analizuje wykonywany kod i kompiluje tylko najbardziej krytyczne fragmenty, oszczędzając czas i pamięć.
Kompilator JIT otrzymuje na wejściu kod bajtowy, interpretuje go i równolegle zbiera statystyki. Gdy jakiś fragment kodu (metoda, pętla) jest wywoływany wystarczająco często, JIT podejmuje decyzję o kompilacji. Skompilowany kod maszynowy jest przechowywany w pamięci podręcznej — przy kolejnych wywołaniach używana jest już gotowa wersja. Zapewnia to przyspieszenie bez konieczności kompilowania całego programu.
// Przykład: metoda stanie się hot po wielokrotnym wywołaniu
public class HotMethod {
private int compute(int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i * i;
}
return sum;
}
}
// Wywołanie 500 razy w pętli — JIT skompiluje compute
for (int t = 0; t < 500; t++) {
hot.compute(1000);
}
W Androidzie kompilacja JIT przeszła trzy fazy ewolucji. Pierwsza faza — Dalvik bez JIT (Android 1.0–2.1): czysta interpretacja kodu bajtowego DEX. Druga faza — Dalvik z JIT (Android 2.2–4.4): pojawienie się kompilatora JIT, który przyspieszył aplikacje 2–5 razy. Trzecia faza — ART z hybrydowym JIT (Android 7.0+): powrót JIT w nowej jakości.
JIT w Dalvik był zaimplementowany jako kompilator trace-based. Analizował nie pojedyncze metody, ale łańcuchy instrukcji (traces), które są często wykonywane sekwencyjnie. Pozwalało to kompilować całe ścieżki wykonania, obejmujące wiele metod. Takie podejście było efektywne dla procesorów mobilnych z małą pamięcią podręczną instrukcji, ponieważ skompilowany trace mieścił się w pamięci podręcznej L1.
Począwszy od Androida 7.0 Nougat, ART używa method-based JIT — kompiluje poszczególne metody na podstawie profili wykonania. Ten JIT działa znacznie szybciej niż Dalvik JIT: typowy czas kompilacji jednej metody to 0.5–1 ms wobec 3–5 ms w Dalvik. Skompilowany kod jest przechowywany w oddzielnym obszarze pamięci (JIT code cache), a nie w stercie aplikacji, co zmniejsza fragmentację.
| Parametr | Dalvik JIT | ART JIT |
|---|---|---|
| Typ | Trace-based | Method-based |
| Szybkość kompilacji | 3–5 ms/metodę | 0.5–1 ms/metodę |
| Próg kompilacji | ~200 wywołań | Dynamiczny |
| Pamięć podręczna kodu | W stercie aplikacji | JIT code cache |
| Profilowanie | Wewnętrzne | Zewnętrzne pliki .prof |
Centralny mechanizm JIT — detekcja hot-metod. Każde wywołanie metody zwiększa wewnętrzny licznik. Gdy licznik przekroczy próg, metoda jest oznaczana jako „gorąca” i wysyłana do kompilacji. W Dalvik próg był sztywno ustawiony (~200 wywołań). W ART liczniki są dostosowywane dynamicznie w zależności od dostępnych zasobów urządzenia.
Proces kompilacji obejmuje kilka faz. Pierwsza — analiza kodu bajtowego: JIT bada strumień instrukcji i buduje graf przepływu danych (data-flow graph). Druga — optymalizacja: inline’owanie małych metod, usuwanie martwego kodu, składanie stałych. Trzecia — generowanie kodu: przekształcanie zoptymalizowanego grafu w instrukcje maszynowe dla konkretnej architektury CPU (ARM, ARM64, x86).
// Demonstracja inliningu — JIT podstawi ciało metody
public int inlineExample() {
return square(5);
}
private int square(int x) {
return x * x;
} // JIT zastąpi wywołanie na return 5 * 5;
Szczególna technika JIT — On-Stack Replacement (OSR). Jeśli metoda zawiera długą pętlę, która nie kończy się przez setki iteracji, JIT może skompilować pętlę „w locie” i zastąpić interpretowaną wersję skompilowaną bezpośrednio podczas wykonania. OSR jest szczególnie efektywny dla zadań obliczeniowych: renderowanie, przetwarzanie obrazów, kryptografia.
JIT i AOT — dwa podejścia do kompilacji z przeciwstawnymi kompromisami. JIT poświęca szybkość pierwszego uruchomienia na rzecz kompaktowości dystrybucji i adaptacyjności. AOT poświęca czas instalacji i miejsce na dysku na rzecz maksymalnej wydajności od pierwszej sekundy. Żadne z podejść nie jest absolutnie lepsze — wybór zależy od scenariusza.
Kluczową zaletą JIT jest optymalizacja adaptacyjna. JIT może wykorzystywać informacje profilowe niedostępne dla AOT: dokładne typy obiektów, rzeczywistą częstotliwość wywołań, faktyczne rozgałęzienia. Pozwala to stosować agresywne optymalizacje niemożliwe przy kompilacji statycznej. Na przykład JIT może wirtualizować wywołania metod (devirtualize), jeśli w praktyce występuje tylko jeden typ odbiorcy.
| Kryterium | JIT | AOT |
|---|---|---|
| Czas instalacji | Natychmiast | Zależy od rozmiaru |
| Pierwsze uruchomienie | Wolniejsze (rozgrzewanie) | Szybkie |
| Miejsce na dysku | Minimalne | +15–30% |
| Adaptacyjność | Wysoka | Niska |
| Zużycie CPU | Szczyty przy kompilacji | Stabilne |
Kompilacja JIT jest preferowana, gdy ważna jest szybkość wdrażania i oszczędność miejsca na dysku. W kontekście rozwoju mobilnego JIT jest idealny dla aplikacji często aktualizowanych (testy A/B, hotfix). JIT jest również wygodny na etapie rozwoju, gdy kod jest przebudowywany dziesiątki razy dziennie — każda zaoszczędzona sekunda na kompilacji przyspiesza cykl informacji zwrotnej.
JIT zapewnia programistom szereg praktycznych zalet. Pierwsza — mały rozmiar APK. Przy podejściu JIT w APK pakowany jest tylko kod bajtowy (DEX), który zajmuje o 20–30% mniej miejsca niż skompilowany kod natywny. Dla użytkowników z ograniczoną pamięcią wbudowaną jest to znacząca zaleta.
Druga zaleta — adaptacja do urządzenia. JIT kompiluje kod z uwzględnieniem rzeczywistej architektury CPU, ilości RAM i bieżącego obciążenia. Na przykład na urządzeniu z 2 GB RAM JIT może kompilować mniej agresywnie, oszczędzając pamięć, a na flagowcu z 12 GB — zastosować wszystkie możliwe optymalizacje. Kompilacja AOT, przeciwnie, ustala decyzję w momencie instalacji.
Kod bajtowy pozostaje niezależny od platformy, co upraszcza dystrybucję aplikacji. Jeden APK działa na urządzeniach ARM, ARM64 i x86, a JIT zapewnia generowanie kodu natywnego dla każdej architektury. Dla podejścia AOT konieczne byłoby albo dołączenie kilku wariantów kodu natywnego w APK (zwiększenie rozmiaru), albo kompilowanie osobnej wersji dla każdej architektury.
Główną wadą JIT jest opóźnienie rozgrzewania (warm-up delay). Użytkownik widzi spowolnienia w pierwszych sekundach działania aplikacji, podczas gdy JIT kompiluje hot-metody. W grach objawia się to jako „jąkanie” (stuttering) na początkowych poziomach. W aplikacjach z animacjami — drgania przy pierwszych przejściach między ekranami.
Drugą wadą jest zużycie energii. Proces kompilacji intensywnie obciąża CPU, zwiększając zużycie energii o 10–20% w okresie rozgrzewania. Na urządzeniach zasilanych baterią skraca to czas pracy na baterii. Szczególnie widoczne w scenariuszach z częstymi restartami aplikacji (wielozadaniowość z ograniczoną pamięcią, gdy system wyładowuje i przeładowuje procesy).
Kolejnym problemem jest fragmentacja pamięci podręcznej JIT. Skompilowany kod jest przechowywany w ciągłym obszarze pamięci. Przy ładowaniu nowych klas i kompilacji dodatkowych metod pamięć podręczna ulega fragmentacji, co zwiększa narzuty na zarządzanie pamięcią. W Dalvik problem ten rozwiązywano przez okresowe czyszczenie pamięci podręcznej; w ART pamięć podręczna JIT jest przydzielana oddzielnie od sterty i używa własnej strategii defragmentacji.
Nowoczesne podejście w ART — kompilacja hybrydowa, łącząca mocne strony JIT i AOT. Podczas instalacji aplikacji kompilacja nie jest wykonywana — tylko weryfikacja kodu bajtowego (verify). Zapewnia to szybką instalację i minimalne zajęte miejsce. Pierwsze uruchomienia działają w trybie interpretacji z kompilacją JIT hot-metod — użytkownik otrzymuje akceptowalną wydajność bez długiego oczekiwania.
Równolegle działa profilator tła, zbierający dane o rzeczywistym użyciu. Po 2–3 pełnych uruchomieniach aplikacji profil osiąga wystarczającą kompletność i system uruchamia dex2oat do kompilacji hot-metod w kod natywny. Ta operacja jest wykonywana w tle, gdy urządzenie nie jest obciążone (ładowanie, ekran wyłączony). Po zakończeniu AOT w tle aplikacja uzyskuje wydajność porównywalną z pełną kompilacją AOT.
# Wymuszone uruchomienie kompilacji w tle
adb shell cmd package compile -m speed-profile -f com.example.app
# Podgląd statusu kompilacji
adb shell cmd package dump-profiles com.example.app
Według danych Google I/O 2017, kompilacja hybrydowa skróciła czas instalacji aplikacji o 30–50% w porównaniu z czystym AOT. Zajmowane miejsce na partycji systemowej zmniejszyło się o 20–30%. Wydajność po kompilacji w tle odpowiada poziomowi pełnego AOT. Jedynym scenariuszem, w którym hybryda ustępuje AOT, jest pierwsze uruchomienie bezpośrednio po instalacji: aplikacja działa w trybie JIT i może być wolniejsza o 10–15%.
Często zadawane pytania
JIT — to sposób na przyspieszenie programu, w którym kod jest tłumaczony na język maszynowy nie z wyprzedzeniem, ale fragmentami podczas działania. Najczęściej używane fragmenty są kompilowane i buforowane, a rzadkie pozostają w oryginalnej postaci.
JIT kompiluje kod podczas wykonywania, co oszczędza miejsce i przyspiesza instalację. AOT kompiluje cały kod z wyprzedzeniem — aplikacja uruchamia się szybciej, ale wymaga więcej miejsca na dysku i czasu na instalację.
JIT nie został usunięty, ale ewoluował. W Androidzie 5.0 Dalvik z JIT zastąpiono ART z czystym AOT. W Androidzie 7.0 JIT powrócił w ART jako część systemu hybrydowego, gdzie działa wspólnie z kompilacją AOT w tle dla optymalnej wydajności.
JIT zwiększa zużycie energii o 10–20% w okresie rozgrzewania z powodu obciążenia CPU. Po zakończeniu kompilacji hot-metod zużycie energii wraca do normalnego poziomu. Tryb hybrydowy ART minimalizuje te szczyty dzięki kompilacji w tle.
Tak, w scenariuszach z intensywnymi obliczeniami. Użytkownik może zauważyć spowolnienia w pierwszych sekundach działania aplikacji lub na początku gry. W nowoczesnych wersjach Androida (8.0+) tryb hybrydowy minimalizuje ten efekt dzięki profilowanej kompilacji.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również