Learn · concepts

Что такое байт-код JVM? Объяснение файлов классов Java

Байткод JVM — это компактный набор инструкций, который выполняет виртуальная машина Java. Узнайте, как работают файлы .class, почему моды переписывают байткод и какое место ASM занимает в этом процессе.

TRtrolPublished 4 min read

Что такое байт-код JVM?

Байт-код JVM — это набор инструкций, который выполняет виртуальная машина Java. Когда вы компилируете исходный код Java, компилятор не создает машинный код для вашего процессора. Он создает байт-код — компактный, переносимый формат, хранящийся в файлах .class. JVM считывает эти инструкции во время выполнения и преобразует их в машинный код по мере необходимости.

Именно такой многослойный подход — от исходного кода к байт-коду и затем к машинному коду — обеспечивает переносимость Java. Один и тот же файл .class может работать в Windows, macOS, Linux или любой другой системе с совместимой JVM, поскольку JVM — это слой, который знает, как выполнять инструкции на локальной машине. Напишите один раз, запускайте где угодно.

Как исходный код становится байт-кодом

Файл .java — это текст, написанный человеком. Файл .class — это скомпилированный вывод, который запускает JVM. Компилятор считывает ваш исходный код, проверяет его и записывает один файл .class на каждый класс, содержащий инструкции байт-кода, а также таблицу констант, таблицы методов и другие метаданные.

Байт-код не привязан к какому-либо конкретному процессору. Один и тот же файл .class может работать в Windows, macOS, Linux или на любом устройстве с Java runtime, поскольку JVM — это слой, который знает, как выполнять инструкции на локальной машине. Именно эта переносимость является сутью формата.

Как выглядят инструкции

Байткод — это набор инструкций, основанный на стеке. Большинство операций помещают значения в операндную стек, извлекают их из неё и помещают результат обратно. Нет необходимости рассматривать распределение регистров; модель намеренно проста, чтобы JVM могла быстро проверять и выполнять её.

Несколько примеров опкодов дают представление об этом:

ОпкодЧто он делает
iloadПомещает локальную переменную типа int в стек
iaddИзвлекает два int из стека, помещает в стек их сумму
invokevirtualВызывает метод экземпляра
getfieldЧитает поле экземпляра
returnВозвращается из метода

Вы редко читаете или пишете их вручную. Инструменты генерируют и потребляют их, и здесь вступает в игру такая библиотека, как ASM.

Почему байткод важен для модов

Байткод важен, потому что он позволяет изменять поведение без исходного кода. Мод для Minecraft почти никогда не имеет исходный код игры. Вместо этого у него есть скомпилированная игра на диске. Чтобы добавить или изменить функцию, мод считывает существующий байткод, переписывает его и передаёт результат обратно в JVM при загрузке классов.

Именно так фреймворки патчинга вставляют хуки в методы, которые им не принадлежат. Они находят правильное место в скомпилированном методе и вставляют вызов кода мода. Работа на уровне байткода означает, что мод может подключаться к игре, даже не видя ни одной строки оригинального Java.

На практике именно так Terminus и другие клиенты на базе Fabric изменяют Minecraft. Вместо наличия исходного кода Minecraft, они работают с скомпилированными файлами .class и переписывают байткод, добавляя такие функции, как модули, экраны настроек и пользовательская отрисовка.

Где ASM вписывается

ASM — это Java-библиотека для чтения, записи и преобразования байткода. Она разбирает файл .class на события или дерево, позволяет коду изменять инструкции и выдаёт обратно валидный файл .class. Она небольшая, быстрая и работает в непосредственной близости от необработанного формата, поэтому более высокоуровневые инструменты моддинга строятся на её основе, а не разбирают файлы классов самостоятельно.

Большинство моддеров никогда напрямую не вызывают ASM. Они используют более высокоуровневый слой, который скрывает опкоды и предоставляет более удобный способ сказать: «запусти мой код в начале этого метода». Под этим слоем происходит редактирование байткода, часто с использованием ASM.

Декомпиляция байткода обратно в исходный код

Декомпиляторы, такие как Fernflower и CFR, берут скомпилированные файлы .class и воссоздают читаемый Java-код из байткода. Процесс не идеален: некоторые имена и комментарии теряются, некоторые конструкции сложнее восстановить, чем другие, а обфусцированный код становится нечитаемым. Но для обычного, неофусцированного кода декомпиляторы обычно выдают результат, достаточно близкий к исходному, чтобы его понять и с ним работать.

Это полезно для изучения работы игры, но не заменяет наличие самого исходного кода. Декомпилированный код по-прежнему является скомпилированной логикой без комментариев, а воссозданные имена переменных могут быть вводящими в заблуждение. Это инструмент для чтения, а не для написания.

Часто задаваемые вопросы