← Назад к вопросам

Что такое JIT-компиляция?

2.0 Middle🔥 151 комментариев
#JVM и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

JIT-компиляция (Just-In-Time Compilation)

JIT-компиляция — это механизм, при котором виртуальная машина Java (JVM) компилирует Java bytecode в машинный код во время выполнения программы. Это один из главных факторов, позволяющих Java достигать высокой производительности.

Традиционный цикл Java выполнения

1. Исходный код (.java)
        ↓ (javac)
2. Java bytecode (.class)
        ↓ (Интерпретация JVM)
3. Выполнение кода
        ↓ (JIT компиляция горячего кода)
4. Машинный код (native code)
        ↓
5. Быстрое выполнение

Как работает JIT

Этап 1: Интерпретация

На начальном этапе JVM интерпретирует bytecode построчно. Это медленнее, но позволяет быстро запустить приложение.

public int calculateSum(int[] numbers) {
    int sum = 0;
    for (int num : numbers) {
        sum += num;  // Эта строка интерпретируется при каждом выполнении
    }
    return sum;
}

// Первые вызовы: медленные (интерпретация)
calculateSum(new int[]{1, 2, 3});
calculateSum(new int[]{4, 5, 6});
calculateSum(new int[]{7, 8, 9});

Этап 2: Профилирование

JVM отслеживает, какие методы вызываются часто (горячий код / hot code).

// Счётчик вызовов (примерный механизм)
private int invocationCount = 0;

public void process() {
    invocationCount++;  // JVM отслеживает
    if (invocationCount > COMPILATION_THRESHOLD) {
        // Передать на JIT компиляцию
    }
}

Этап 3: JIT-компиляция

Когда метод вызывается достаточно часто, JIT компилирует его в машинный код.

Bytecode метода:        JIT Compiler        Машинный код:
aload_0                 ↓                  mov eax, [rdi]
aload_1             Оптимизация:          add eax, [rsi]
add                 - Inlining             jmp 0x1234
astore_2            - Loop unrolling
                    - Dead code elimination
                         ↓

Этап 4: Выполнение оптимизированного кода

После JIT компиляции метод выполняется как чистый машинный код — очень быстро.

Пример с визуализацией

public class JITExample {
    public static long fibonacci(int n) {
        if (n <= 1) return n;
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
    
    public static void main(String[] args) {
        // Вызовы 1-1000: интерпретируются (медленно)
        for (int i = 0; i < 1000; i++) {
            fibonacci(30);
        }
        
        // После ~1000 вызовов fibonacci компилируется JIT в машинный код
        // Вызовы 1001+: выполняются очень быстро (машинный код)
        for (int i = 1000; i < 2000; i++) {
            fibonacci(30);
        }
    }
}

// Примерное время выполнения:
// Вызовы 1-1000: ~500ms (интерпретация)
// Вызовы 1001-2000: ~5ms (машинный код) — в 100 РАЗ быстрее!

Пороги компиляции

JVM имеет пороги, определяющие когда компилировать метод:

// Запуск JVM с разными порогами
java -XX:CompileThreshold=10000 MyApp          // Компилировать после 10000 вызовов
java -XX:TieredStopAtLevel=4 MyApp            // 4-уровневая оптимизация
java -XX:+PrintCompilation MyApp              // Вывод информации о компиляции

// Вывод:
// 123  s bn  java.lang.String::hashCode (55 bytes)
//      ^    ^
//      |    method compiled
//      compilation ID

JIT-оптимизации

1. Inlining

Подстановка кода вызываемого метода в место вызова.

public int getValue() {
    return 42;  // Простой метод
}

public void useValue() {
    int x = getValue();  // После JIT: x = 42 (без вызова функции)
    System.out.println(x);
}

// Bytecode:          После JIT:     
// invokevirtual  →   nop (inline)
// astore_1       →   bipush 42
// astore_1

2. Loop Unrolling

Разворачивание циклов для уменьшения проверок.

// Исходный цикл
for (int i = 0; i < 4; i++) {
    array[i] *= 2;
}

// После unrolling
array[0] *= 2;  // Без проверки условия i < 4
array[1] *= 2;  // 
array[2] *= 2;  //
array[3] *= 2;  //

3. Dead Code Elimination

Удаление недостижимого или ненужного кода.

public int calculate(boolean flag) {
    if (flag) {
        return 10;  // JIT знает, что флаг всегда true
    } else {
        // Этот код удаляется
        return 20;
    }
}

// После JIT:
public int calculate(boolean flag) {
    return 10;  // Просто возвращаем значение
}

4. Escape Analysis

Оптимизация алокации объектов, которые не выходят за пределы метода.

public int processData() {
    Point p = new Point(10, 20);  // После JIT это не создаёт объект
    return p.x + p.y;             // Вычисляется как 30 без алокации
}

Двухуровневая компиляция (Tiered Compilation)

Современная JVM использует несколько уровней компиляции:

Уровень 0: Интерпретация (очень медленно, но быстрый старт)
        ↓
Уровень 1: C1 JIT компилятор (быстрая компиляция, умеренные оптимизации)
        ↓
Уровень 2: Переход на уровень 2 для горячего кода
        ↓
Уровень 3: C2 JIT компилятор (медленная компиляция, агрессивные оптимизации)
        ↓
Уровень 4: Переход на 4-й уровень для самого горячего кода (профилирование)

Deoptimization

Если предположение JIT неверно, код откатывается.

public class Shape {
    public int getArea() { return 0; }
}

public class Circle extends Shape {
    public int getArea() { return 3.14 * r * r; }
}

public void process(Shape s) {
    int area = s.getArea();
    // JIT может предположить, что это всегда Circle
    // и inlineать Circle.getArea()
    // Если появится новый подкласс, происходит deoptimization
}

Влияние на производительность

public class PerformanceComparison {
    public static long sum(int[] arr) {
        long sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }
    
    public static void main(String[] args) {
        int[] arr = new int[10000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
        
        // Вызовы 1-999: интерпретация, медленно
        for (int i = 0; i < 999; i++) {
            sum(arr);
        }
        
        // Вызов 1000+: JIT компиляция, очень быстро
        long start = System.nanoTime();
        for (int i = 1000; i < 2000; i++) {
            sum(arr);
        }
        long end = System.nanoTime();
        
        System.out.println("Время: " + (end - start) / 1_000_000 + "ms");
        // Результат: ~1ms (оптимизировано JIT)
    }
}

Просмотр JIT компиляции

# Вывести информацию о JIT компиляции
java -XX:+PrintCompilation MyApp

# Вывести более подробное logfile
java -XX:+TraceClassLoading -XX:+UnlockDiagnosticVMOptions \
    -XX:+PrintCompilation MyApp > compilation.log

# Использовать инструмент JITWatch для визуализации
jitwatch myapp.log

Ключевые выводы

  • JIT компилирует горячий код в машинный код во время выполнения
  • Первые вызовы медленные (интерпретация), последующие быстрые (машинный код)
  • JVM применяет интеллектуальные оптимизации (inlining, loop unrolling и т.д.)
  • Это позволяет Java быть конкурентоспособной с C/C++ по производительности
  • Деоптимизация происходит, если предположения JIT неверны
Что такое JIT-компиляция? | PrepBro