Какие стадии обработки проходит Java-программа перед выполнением байткода
Что проверяют
Представляете ли вы путь от .java до нативных инструкций и понимаете ли, что часть работы происходит уже во время выполнения. По ответу видно, читал ли кандидат про JVM или знает только «javac компилирует в байткод». Следом обычно спрашивают про JIT или порядок инициализации класса.
Как отвечать
Разложите на стадии и назовите, что делает каждая:
- компиляция:
javacразбирает исходник, проверяет типы, прогоняет обработчики аннотаций (Lombok и MapStruct работают именно тут) и выдаёт.classс байткодом и пулом констант. Оптимизаций почти нет: свёртка констант и разворачивание синтаксического сахара; - загрузка: класс находит загрузчик, иерархия с Java 9 это bootstrap, platform и application с делегированием вверх. Загрузка ленивая, по первой необходимости;
- связывание: верификация байткода (типы на стеке проверяются по
StackMapTable), подготовка, где статическим полям выставляются значения по умолчанию, и разрешение символьных ссылок, обычно тоже ленивое; - инициализация: выполняется
<clinit>, то есть статические блоки и инициализаторы полей, ровно один раз и потокобезопасно; - исполнение: сначала интерпретатор, параллельно собирается профиль. HotSpot компилирует многоуровнево: C1 быстро и с профилированием, C2 агрессивно, с инлайнингом и анализом убегания. Нарушилось спекулятивное допущение, значит деоптимизация обратно в интерпретатор.
Байткод переносим, а нативный код нет, отсюда прогрев и медленные первые запросы. Есть и AOT: native-image у GraalVM собирает исполняемый файл заранее, а в свежих JDK появился кэш заранее загруженных и связанных классов из проекта Leyden.
Где спотыкаются
Говорят, что JVM просто интерпретирует байткод. Горячий код давно исполняется как нативный, и без этого производительность Java была бы совсем другой.
Считают, что безопасность байткода гарантирует javac. Проверяет его именно JVM при загрузке, потому что .class мог быть собран чем угодно и изменён после сборки.
Не отличают загрузку класса от инициализации. Обращение к константе времени компиляции класс не инициализирует, а первое обращение к статическому полю или конструктору запускает <clinit>.