Как код попадает в JVM
Что проверяют
Понимаете ли вы весь путь от .java до машинных инструкций, а не только «javac компилирует, JVM запускает». Следом обычно спрашивают про загрузчики классов и делегирование или про то, почему код разгоняется не сразу.
Как отвечать
Пройдите по этапам:
- компиляция:
javacпревращает исходник в.classс байт-кодом, пулом констант и метаданными. Байт-код не зависит от платформы, оптимизаций на этом этапе почти нет; - загрузка:
ClassLoaderнаходит класс по имени и читает байты. Иерархия: bootstrap (модульjava.base), platform, application (classpath). Сначала запрос уходит родительскому загрузчику, это защищает от подменыjava.lang.String; - линковка: верификация байт-кода, подготовка (статическим полям выделяется память и значения по умолчанию), разрешение символических ссылок, часто лениво;
- инициализация: выполняется
<clinit>, то есть статические инициализаторы. Происходит при первом активном использовании класса, а не при старте программы; - исполнение: сначала интерпретатор, затем JIT. В HotSpot многоуровневая компиляция: C1 быстро компилирует тёплые методы, C2 по собранному профилю агрессивно оптимизирует горячие, с инлайнингом и escape analysis.
Стоит упомянуть, что с Java 11 однофайловую программу можно запустить как java Main.java без явного javac, компиляция идёт в памяти. Для быстрого старта есть CDS и AppCDS с готовым архивом классов и GraalVM Native Image, где классический рантайм с JIT не нужен вовсе.
Где спотыкаются
Говорят, что JVM всё время интерпретирует байт-код построчно. Горячий код в долгоживущем сервисе почти целиком работает скомпилированным JIT.
Путают загрузку и инициализацию. Обращение к Foo.class или к константе времени компиляции статический блок не запускает, а new Foo() запускает.
Не могут объяснить, зачем нужно делегирование, и не знают, что класс, загруженный двумя разными загрузчиками, для JVM это два разных типа. Отсюда ClassCastException между «одинаковыми» классами в серверах приложений.