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

Что делает JVM при вызове метода десять тысяч раз?

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

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

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

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

JVM и JIT-компиляция при многократном вызове метода

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

Что происходит при 10,000 вызовах

JIT-порог (JIT threshold) — это число вызовов метода, при достижении которого JVM начинает компилировать его в машинный код. По умолчанию этот порог составляет примерно 10,000 вызовов (в HotSpot JVM с флагом -XX:CompileThreshold=10000). Это не просто совпадение в твоём вопросе!

Фазы исполнения

1. Интерпретация (вызовы 1-9,999)

Сначала JVM интерпретирует байт-код построчно:

public class MethodInvocationExample {
    public static int fibonacci(int n) {
        if (n <= 1) return n;
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
    
    public static void main(String[] args) {
        // Первые 9,999 вызовов — интерпретация
        for (int i = 0; i < 10_000; i++) {
            fibonacci(10);
        }
    }
}

2. JIT-компиляция (вызов ~10,000)

Когда счётчик вызовов достигает порога, JVM:**

  • Анализирует метод и его поведение
  • Компилирует в машинный код (native code)
  • Кэширует результат в коде
  • Оптимизирует на основе собранных статистик

3. Исполнение скомпилированного кода (вызовы 10,001+)

Вызовы после компиляции выполняются в 10-100 раз быстрее!

Оптимизации, которые делает JIT

Inline-подстановка (Method Inlining):

// ДО компиляции:
for (int i = 0; i < 10_000; i++) {
    int result = getValue();  // вызов функции
}

// ПОСЛЕ JIT компиляции:
for (int i = 0; i < 10_000; i++) {
    int result = 42;  // функция подставлена прямо
}

Escape Analysis: JIT определяет, остаётся ли объект локально, и может избежать выделения памяти в heap:

public class Point {
    private int x, y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

// JIT поймёт, что объект локальный и может оптимизировать
private int calculateDistance() {
    Point p = new Point(10, 20);  // может быть на стеке
    return p.x + p.y;
}

Branch Prediction: JIT оптимизирует условные переходы на основе собранной статистики:

public int process(int[] values) {
    int sum = 0;
    for (int v : values) {
        if (v > 0) {  // JIT помнит, что в 99% случаев условие истинно
            sum += v;  // оптимизирует pipeline процессора
        }
    }
    return sum;
}

Различные уровни компиляции

HotSpot JVM использует tiered compilation:

  • Уровень 0: Интерпретация
  • Уровень 1-3: C1 JIT (быстрая компиляция, умеренные оптимизации)
  • Уровень 4: C2 JIT (медленная компиляция, агрессивные оптимизации)
// Запуск с информацией о компиляции:
// java -XX:+PrintCompilation ClassName

Практический пример

public class JITDemonstration {
    public static long fibonacci(int n) {
        if (n <= 1) return n;
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
    
    public static void main(String[] args) {
        long startTime = System.nanoTime();
        
        // Первые 10,000 вызовов медленные (интерпретация)
        for (int i = 0; i < 10_000; i++) {
            fibonacci(20);
        }
        
        long midTime = System.nanoTime();
        System.out.println("Интерпретация: " + (midTime - startTime) / 1_000_000 + " мс");
        
        // Следующие вызовы быстрые (JIT-скомпилировано)
        for (int i = 0; i < 10_000; i++) {
            fibonacci(20);
        }
        
        long endTime = System.nanoTime();
        System.out.println("JIT-компилированный: " + (endTime - midTime) / 1_000_000 + " мс");
    }
}

Дезоптимизация (Deoptimization)

Интересный факт: JIT может отменить компиляцию, если его предположения оказались неправильными:

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

public Animal getAnimal(int type) {
    // JIT скомпилирует, предполагая, что всегда Dog
    return (type == 1) ? new Dog() : new Dog();
}

// Но если потом начнём возвращать Cat — дезоптимизация!

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

  • 10,000 вызовов — это критическая точка для JIT-компиляции
  • До компиляции: интерпретация (медленно)
  • После компиляции: машинный код (быстро, в 10-100 раз)
  • JIT анализирует горячий код на лету и оптимизирует его
  • Это главная причина, почему Java-приложения разгоняются со временем

Это огромное преимущество Java перед чистым интерпретируемым языком: программа автоматически оптимизируется во время работы!

Что делает JVM при вызове метода десять тысяч раз? | PrepBro