Что получается на выходе после компиляции Java-кода
Что проверяют
Понимаете ли вы двухступенчатую модель исполнения: компилятор отдаёт байт-код, а машинный код появляется позже и делает его другой инструмент. Вопрос базовый, на нём отсеивают тех, кто учил Java по верхам.
Как отвечать
javac превращает файлы .java в файлы .class с байт-кодом для виртуальной машины. Байт-код не зависит от процессора и операционной системы, в этом и состоит переносимость.
Важная деталь: один файл исходника даёт не обязательно один класс. Вложенные и внутренние классы получают имена вида Outer$Inner.class, анонимные нумеруются как Outer$1.class, плюс компилятор добавляет синтетические методы-мосты для дженериков.
Внутри .class лежат пул констант, флаги доступа, поля, методы с инструкциями байт-кода, таблицы исключений и отладочные атрибуты вроде таблицы номеров строк, если сборка шла с отладочной информацией.
Компилятор оптимизирует мало: подставляет константы времени компиляции, стирает дженерики, переводит лямбды и конкатенацию строк на invokedynamic. Настоящая оптимизация происходит в рантайме: горячие методы компилирует JIT по собранному профилю. Дальше классы пакуются в jar, а если нужен бинарник без виртуальной машины, это уже отдельная сборка через опережающую компиляцию.
Где спотыкаются
Думают, что javac оптимизирует код. Практический вывод обратный: микрозамеры на уровне исходника обычно ничего не доказывают, потому что решение принимает JIT, и наивный бенчмарк без прогрева меряет интерпретатор.
Второе: удивляются набору файлов с долларом в имени и не могут объяснить, откуда они. А из этого же незнания растут ошибки при рефлексии и при ручной работе с путями к классам.
Третье: говорят, что виртуальная машина просто интерпретирует байт-код. Режим смешанный: сначала интерпретация, потом компиляция горячих участков, а при нарушении предположений происходит деоптимизация обратно в интерпретатор.