Компиляция и интерпретация

Правильнее классифицировать не язык, а конкретную реализацию и путь выполнения программы. Один язык может иметь интерпретатор, JIT-компилятор и AOT-компилятор.

ПодходКогда переводится кодЧто исполняется
ahead-of-time compilationдо запускамашинный код или промежуточное представление
interpretationво время запуска, инструкция за инструкциейисходный код или bytecode
just-in-time compilationво время работы по профилюскомпилированные горячие участки

Путь Java

Main.java
   │ javac
   ▼
Main.class (bytecode)
   │ class loading + verification
   ▼
интерпретация → профилирование → JIT-компиляция горячего кода

javac проверяет типы и переводит исходник в JVM bytecode. JVM сначала может интерпретировать методы, собирая профиль, а затем JIT-компилятор оптимизирует часто выполняемый код под конкретный процессор. Оптимизированный метод может быть деоптимизирован, если предположения перестали выполняться.

public static int sum(int[] values) {
    int result = 0;
    for (int value : values) {
        result += value;
    }
    return result;
}

Этот метод один и тот же в .class, но машинный код зависит от JVM, профиля нагрузки и архитектуры CPU.

Компромиссы

  • AOT сокращает прогрев, но имеет меньше runtime-профиля;
  • интерпретатор быстро начинает выполнение, но медленнее на горячем коде;
  • JIT тратит CPU/память на компиляцию и требует прогрева, зато использует реальные профили;
  • переносимость bytecode обеспечивается JVM, а не отсутствием компиляции.

На собеседовании

Java — компилируемый или интерпретируемый язык? Обычный HotSpot-путь сочетает компиляцию в bytecode, интерпретацию и JIT.

Почему benchmark надо прогревать? До JIT-компиляции и стабилизации профиля измеряется другой режим.

Что такое деоптимизация? Возврат от оптимизированного кода, когда его спекулятивные предположения стали неверны.

Bytecode — машинный код? Нет, это инструкции виртуальной машины.

Связано: Структура JDK, Class loader.