Jak kompilator JIT optymalizuje kod
Gdy metoda jest wybierana do kompilacji, maszyna JVM przekazuje jej kod bajtowy do kompilatora JIT (Just-In-Time). JIT musi zrozumieć semantykę i składnię kodu bajtowego, zanim będzie mógł poprawnie skompilować metodę.
Aby ułatwić kompilatorowi JIT analizę tej metody, jej kody bajtowe są najpierw ponownie sformułowane w wewnętrznej reprezentacji o nazwie trees, która przypomina kod maszynowy dokładniej niż kod bajtowy. Następnie analiza i optymalizacje są wykonywane na drzewach metody. Na końcu drzewa są tłumaczone na kod rodzimy. Pozostała część tej sekcji zawiera krótki przegląd faz kompilowania JIT. Więcej informacji na ten temat zawiera sekcja Diagnozowanie problemu z JIT lub AOT.
Kompilator JIT może używać więcej niż jednego wątku kompilacji do wykonywania zadań kompilacji JIT. Korzystanie z wielu wątków może potencjalnie pomóc aplikacjom Java szybciej uruchamiać aplikacje. W praktyce wiele wątków kompilacji JIT wykazuje poprawę wydajności tylko wtedy, gdy w systemie są nieużywane rdzenie przetwarzania.
Kompilacja składa się z następujących faz. Wszystkie fazy z wyjątkiem generowania kodu rodzimego to kod wieloplatformowy.
Faza 1-inokacja
- Trivial inlining
- Inkreślenie wykresu wywołania
- Eliminacja rekurencji tylnej
- Optymalizacje ochrony przed zgłosami wirtualnymi
Faza 2-optymalizacje lokalne
- Lokalne analizy przepływu danych i optymalizacje
- Rejestrowanie optymalizacji wykorzystania
- Uproszczenia idiomów Java
Faza 3-optymalizacje przepływu sterowania
- Zmiana kolejności, podział i usuwanie kodu
- Redukcja pętli i inwersja
- Pętla strugająca i pętla-nieinwazyjny ruch kodu
- Pętla unrolling i peeling
- Kontrola wersji pętli i specjalizacja
- Optymalizacja ukierunkowanych wyjątków
- Analiza przełączników
Faza 4-optymalizacje globalne
- Globalne analizy przepływu danych i optymalizacje
- Częściowa korekta nadmiarowości
- Analiza zmiany znaczenia
- Optymalizacje czyszczenia pamięci i przydzielania pamięci
- Optymalizacje synchronizacji
Faza 5-generowanie kodu rodzimego
Procesy generowania kodu rodzimego różnią się w zależności od architektury platformy. Ogólnie, podczas tej fazy kompilacji drzewa metody są tłumaczone na instrukcje kodu maszynowego; niektóre małe optymalizacje są wykonywane zgodnie z charakterystyką architektury. Skompilowany kod jest umieszczany w części obszaru procesu maszyny JVM o nazwie pamięć podręczna kodu. Położenie metody w pamięci podręcznej kodu jest rejestrowane, tak więc przyszłe wywołania do niej będą wywoływane skompilowany kod. W danym momencie proces JVM składa się z plików wykonywalnych JVM i zestawu skompilowanego kodu JIT, który jest dynamicznie dowiązany do interpretera kodu bajtowego w maszynie JVM.