java просмотр байт кода
Введение в байт-код Java
May 15 · 6 min read
Каждому Java-разработчику известно, какую роль в экосистеме языка играет JVM. Однако большинство не разбирается в том, как работает JVM под капотом. Хотя для разработки на Java это не обязательно, код станет лучше, если вы глубже поймете JVM, потому что так вы будете знать, как каждая строка кода влияет на процессы внутри JVM.
Однако для начала нужно понять, что такое байт-код. Итак, поговорим о вводе и выводе байт-кода Java и о том, как он влияет на JVM во время запуска программы.
Что такое байт-код Java?
Если в какой-то момент профессиональной жизни вы слышали, как проповедуют независимость Java-программ от платформ, скажите спасибо байт-коду.
Как генерируется байт-код?
Как посмотреть байт-код Java?
Если вам хочется увидеть сам байт-код, простейший способ — воспользоваться командной строкой.
Как работает JVM
Прежде чем углубляться в байт-код, стоит понять, как JVM его обрабатывает.
Методы — одна из важнейших составляющих кода для JVM. Среда выполнения Java-программы — это, по сути, набор методов, вызываемых JVM. JVM создает фрейм для каждого такого метода и помещает созданный фрейм наверх стека текущего потока для выполнения.
Фрейм состоит из локальной среды, которая необходима для поддержания его выполнения. Как правило он содержит массив локальных переменных и стек операндов. Посмотрим, что эти элементы из себя представляют.
Массив локальных переменных
Массив локальных переменных, как следует из названия, нужен для хранения локальных переменных в методе. Также он хранит аргументы, которые принимает метод.
Определим два метода: один статический и один метод экземпляра, но схожие во всем остальном.
Локальные массивы переменных для этих методов будут выглядеть следующим образом:
Стек операндов
Стек операндов — это рабочее пространство внутри фрейма метода. Поскольку это стек, вы можете помещать и забирать значения только из верхней его части. Большинство инструкций байт-кода, принадлежащих определенному методу, либо участвуют в помещении значений в стек, либо забирают значения из стека для обработки.
Инструкция байт-кода load и ее расширения нужны для перемещения значения, хранящегося в массиве переменных, в стек. Инструкция store применяется для извлечения значений из стека и сохранения в массиве переменных. Существуют и другие инструкции, которые извлекают значения из стека для обработки.
Посмотрим в байт-код
Ради возможности вглядеться в байт-код, я написал простой Java-класс:
Деконструкция байт-кода
Здесь важно отметить еще одно: индексы, заданные инструкциям байт-кода — как видим, они не увеличиваются на единицу для каждой новой инструкции.
Число перед инструкцией указывает на индекс ее начального байта. А любой байт-код состоит из однобайтовых опкодов, за которыми следует ноль или более операндов.
Вывод
Надеюсь, вам удалось узнать кое-что новое о том, как работает байт-код Java. С этим более четким знанием вы сможете лучше писать код. Можете даже поэкспериментировать с самим байт-кодом во время выполнения программы, воспользовавшись такими библиотеками, как ASM.
Введение в байт-код Java
Каждому Java-разработчику известно, какую роль в экосистеме языка играет JVM. Однако большинство не разбирается в том, как работает JVM под капотом. Хотя для разработки на Java это не обязательно, код станет лучше, если вы глубже поймете JVM, потому что так вы будете знать, как каждая строка кода влияет на процессы внутри JVM.
Однако для начала нужно понять, что такое байт-код. Итак, поговорим о вводе и выводе байт-кода Java и о том, как он влияет на JVM во время запуска программы.
Что такое байт-код Java?
Если в какой-то момент профессиональной жизни вы слышали, как проповедуют независимость Java-программ от платформ, скажите спасибо байт-коду.
Байт-код — это набор команд, который JVM применяет для запуска программы. Поскольку байт-код, сгенерированный для программы, не зависит от платформы, где она запущена, вы можете без проблем запускать свою программу на любой машине, на которой есть JVM для интерпретации байт-кода.
Как генерируется байт-код?
Как посмотреть байт-код Java?
Если вам хочется увидеть сам байт-код, простейший способ — воспользоваться командной строкой.
Как работает JVM
Прежде чем углубляться в байт-код, стоит понять, как JVM его обрабатывает.
Методы — одна из важнейших составляющих кода для JVM. Среда выполнения Java-программы — это, по сути, набор методов, вызываемых JVM. JVM создает фрейм для каждого такого метода и помещает созданный фрейм наверх стека текущего потока для выполнения.
Фрейм состоит из локальной среды, которая необходима для поддержания его выполнения. Как правило он содержит массив локальных переменных и стек операндов. Посмотрим, что эти элементы из себя представляют.
Массив локальных переменных
Массив локальных переменных, как следует из названия, нужен для хранения локальных переменных в методе. Также он хранит аргументы, которые принимает метод.
Определим два метода: один статический и один метод экземпляра, но схожие во всем остальном.
Локальные массивы переменных для этих методов будут выглядеть следующим образом:
Стек операндов
Стек операндов — это рабочее пространство внутри фрейма метода. Поскольку это стек, вы можете помещать и забирать значения только из верхней его части. Большинство инструкций байт-кода, принадлежащих определенному методу, либо участвуют в помещении значений в стек, либо забирают значения из стека для обработки.
Инструкция байт-кода load и ее расширения нужны для перемещения значения, хранящегося в массиве переменных, в стек. Инструкция store применяется для извлечения значений из стека и сохранения в массиве переменных. Существуют и другие инструкции, которые извлекают значения из стека для обработки.
Посмотрим в байт-код
Ради возможности вглядеться в байт-код, я написал простой Java-класс:
Деконструкция байт-кода
Здесь важно отметить еще одно: индексы, заданные инструкциям байт-кода — как видим, они не увеличиваются на единицу для каждой новой инструкции.
Число перед инструкцией указывает на индекс ее начального байта. А любой байт-код состоит из однобайтовых опкодов, за которыми следует ноль или более операндов.
Вывод
Надеюсь, вам удалось узнать кое-что новое о том, как работает байт-код Java. С этим более четким знанием вы сможете лучше писать код. Можете даже поэкспериментировать с самим байт-кодом во время выполнения программы, воспользовавшись такими библиотеками, как ASM.
Русские Блоги
Просмотр байт-кода Java в командной строке, eclipse, Intellij IDEA
В процессе нашей работы, изучения и исследования JVM неизбежно проверять байт-код Java. Проверяя байт-код, мы можем понять результат компиляции класса, а также можем проанализировать производительность класса на уровне компилятора.
Файлы байт-кода нельзя открыть напрямую.Следующий лидер стека учит вас, как просматривать байт-код Java несколькими простыми способами.
1. Используйте команду javap для просмотра байт-кода.
JDK предоставляет это без объяснения причин, давайте продемонстрируем работу:
Как показано выше, вы можете увидеть байт-код класса Test.
2. Просмотр байт-кода в Intellij IDEA
Intellij IDEA напрямую интегрирует меню инструментов, вы можете напрямую просматривать байт-код, открывать ByteCode Метод окна плагина выглядит следующим образом:
Как показано на рисунке, вы можете увидеть байт-код класса String.
Если вы не видите это меню, возможно, ваш текущий класс не скомпилирован и вам необходимо его скомпилировать.
3. Просмотрите байт-код в Eclipse.
Просмотр байт-кода в Eclipse немного хлопотный, вам нужно установить плагин bytecode 。
Name: bytecode
Location: http://andrei.gmxhome.de/eclipse
После установки вам необходимо перезапустить Eclipse, чтобы он вступил в силу.
Затмение открывается ByteCode Окно плагина:
Как показано на рисунке, вы можете увидеть байт-код класса String.
Конечно, на рынке есть много других гаджетов, которые могут просматривать байт-код, поэтому я не буду рассказывать о них здесь по отдельности. Те, кому интересно, могут узнать больше.
Интеллектуальная рекомендация
Механизм мониторинга в Vue
В повседневном развитии, мы можем часто передать значение переменной, чтобы контролировать определенную логику. Например, когда мой TEPM значение TRUE, мне нужно добавить переменную 1. Опять же, это б.
[leetcode]Count and Say
The count-and-say sequence is the sequence of integers with the first five terms as following: 1 is read off as «one 1» or 11. 11 is read off as «two 1s&qu.
SessionStorage, LocalStorage и cookie в JavaScript
SessionStorage, LocalStorage и cookie в JavaScript Добро пожаловать на мой блогЖелаю сельских соотечественниках, я собирался на вершину жизни, и я выйду замужем Bai Fumei
Введение в байт-код Java
Каждому Java-разработчику известно, какую роль в экосистеме языка играет JVM. Однако большинство не разбирается в том, как работает JVM под капотом. Хотя для разработки на Java это не обязательно, код станет лучше, если вы глубже поймете JVM, потому что так вы будете знать, как каждая строка кода влияет на процессы внутри JVM.
Однако для начала нужно понять, что такое байт-код. Итак, поговорим о вводе и выводе байт-кода Java и о том, как он влияет на JVM во время запуска программы.
Что такое байт-код Java?
Если в какой-то момент профессиональной жизни вы слышали, как проповедуют независимость Java-программ от платформ, скажите спасибо байт-коду.
Байт-код — это набор команд, который JVM применяет для запуска программы. Поскольку байт-код, сгенерированный для программы, не зависит от платформы, где она запущена, вы можете без проблем запускать свою программу на любой машине, на которой есть JVM для интерпретации байт-кода.
Как генерируется байт-код?
Как посмотреть байт-код Java?
Если вам хочется увидеть сам байт-код, простейший способ — воспользоваться командной строкой.
Как работает JVM
Прежде чем углубляться в байт-код, стоит понять, как JVM его обрабатывает.
Методы — одна из важнейших составляющих кода для JVM. Среда выполнения Java-программы — это, по сути, набор методов, вызываемых JVM. JVM создает фрейм для каждого такого метода и помещает созданный фрейм наверх стека текущего потока для выполнения.
Фрейм состоит из локальной среды, которая необходима для поддержания его выполнения. Как правило он содержит массив локальных переменных и стек операндов. Посмотрим, что эти элементы из себя представляют.
Массив локальных переменных
Массив локальных переменных, как следует из названия, нужен для хранения локальных переменных в методе. Также он хранит аргументы, которые принимает метод.
Определим два метода: один статический и один метод экземпляра, но схожие во всем остальном.
Локальные массивы переменных для этих методов будут выглядеть следующим образом:
Стек операндов
Стек операндов — это рабочее пространство внутри фрейма метода. Поскольку это стек, вы можете помещать и забирать значения только из верхней его части. Большинство инструкций байт-кода, принадлежащих определенному методу, либо участвуют в помещении значений в стек, либо забирают значения из стека для обработки.
Инструкция байт-кода load и ее расширения нужны для перемещения значения, хранящегося в массиве переменных, в стек. Инструкция store применяется для извлечения значений из стека и сохранения в массиве переменных. Существуют и другие инструкции, которые извлекают значения из стека для обработки.
Посмотрим в байт-код
Ради возможности вглядеться в байт-код, я написал простой Java-класс:
Деконструкция байт-кода
Здесь важно отметить еще одно: индексы, заданные инструкциям байт-кода — как видим, они не увеличиваются на единицу для каждой новой инструкции.
Число перед инструкцией указывает на индекс ее начального байта. А любой байт-код состоит из однобайтовых опкодов, за которыми следует ноль или более операндов.
Вывод
Надеюсь, вам удалось узнать кое-что новое о том, как работает байт-код Java. С этим более четким знанием вы сможете лучше писать код. Можете даже поэкспериментировать с самим байт-кодом во время выполнения программы, воспользовавшись такими библиотеками, как ASM.
Основы Java Bytecode
Что будет и кому может быть интересно
Внимание, статья содержит довольно много картинок и получилась довольно тяжелой и объемной
В данной статье будут рассмотрены только основы Java Bytecode. Если вы уже знакомы с его основами, статья вряд ли будет вам интересна, так как практически все можно найти в документации.
В данной статье не рассмотрены многие темы (например, фреймы, многие атрибуты), иначе она бы получилась еще больше
Зачем знать что-то о Bytecode
Тема bytecode довольно скучная и в реальной работе среднестатистического программиста практически не используется.
Так почему стоит знать про основы bytecode:
Потому что с этим работает Java Machine и хочется понимать, что лежит в основе
Потому что многие современные фреймворки что-то тихо делают на уровне bytecode и часто могут что-то там сломать (привет, Lombok)
Потому что просто стало скучно 🙂
Для того чтобы просмотреть содержимое, можно воспользоваться стандартной утилитой
javap может принимать много параметров. Давайте рассмотрим основные из них.
Смотреть будем на стандартном классе java.lang.Object
Без параметров
Выводится только основная информация по классам, методам и полям (приватные поля и методы не показываются)
Показываются также приватные поля и методы
Показываются подробную информацию (verbose), такую, как размер стека и аргументов, версии и т.д.
В IDEA имеет встроенные средства для просмотра:
Просмотр для Java
Просмотр для Kotlin
Версии class файлов
Чтобы понять, какие версии class файлов какая jvm поддерживает, можно воспользоваться таблицей из документации:
Поддерживаемые major версии
Для версии Java, начиная с 5, работает формула Major-44=Версия Java
access_flags
Flag Name (Имя флага)
public, виден за пределами пакета
final ; не может быть наследован
Обработка методов супер-класса когда вызывается инструкция invokespecial. (ссылка на более подробное объяснение)
Интерфейс, не класс
Абстрактный, не может быть наследован
Синтетический, не из кода
Модуль, не класс или интерфейс
Внутрь Bytecode
Рассмотрим первый простой файл:
Версия 55.0, что соответствует версии Java 11 (55-44=11)
Рассмотрим теперь файл с каким-нибудь полем :
Часть bytecode, касающаяся поля:
Для полей приведены флаги доступа, имя, описание (сигнатура поля) и атрибуты (здесь, ConstantValue)
Сигнатуры (descriptor) для полей имеют следующий формат:
Описание структуры метода
Рассмотрим простой класс:
Документацию можно посмотреть тут.
Выполнение метода
Основы
Продолжим работать с предыдущим примером.
Инструкции JVM занимают 1 байт, затем следуют параметры инструкции (если они есть).
iconst_1. Кладет на стек 1.
istore_1. Снимает значение с вершины стека и записывает в локальную переменную под индексом 1
iload_1. Записывает на вершину стека значение из переменной под индексом 1.
iconst_2. Записывает на вершину стека значение 2.
iadd. Снимает с вершины стека два целых числа, суммирует их и кладет полученный результат на вершину стека
istore_2. Снимает значение с вершины стека и записывает в локальную переменную под индексом 2
getstatic #2. Находит ссылку на статическое поле указанное в пуле под индексом #2 и кладет ее на вершину стека
iload_2. Записывает на вершину стека значение из переменной под индексом 2.
invokevirtual #3. Находит метод в пуле под #3, снимает требуемое количество значений из вершины стека для использования как параметры, снимает ссылку на объект и выполняет метод. Если метод что-то возвращает, кладет обратно на вершину стека.
Как это выглядит в динамике (анимация):
Реализация if
Рассмотрим такой код:
Реализация циклов
Циклы стандартно реализуются аналогично if.
Рассмотрим исходный код и его байткод:
Реализация switch
Реализация throw
При выполнении он выведет:
Здесь добавилась Exception table, в которой строки отсортированы таким образом, что виртуальная машина проверяет соответствие строки и вида исключения сверху вниз с последней точки, где она остановилась.
Когда мы дошли до 9 строчки, виртуальная машина выбросила исключение. И начала смотреть в Exception table. Первая запись удовлетворяет условиям использования (строчка 9 находится в интервале от 0 до 10 и указан RuntimeException) и поэтому выполняется переход на строчку 10.
Дальше мы доходим до строчки 31 и снова выполняется выброс исключения. Смотреть мы теперь начинаем с 2 строчки ExceptionTable. Она нам не подходит и поэтому переходим на 3, которая подходит, и выполняем переход на 32 строчку.
Вызовы методов (opcode: invokestatic, invokespecial, invokevirtual, invokeinterface, invokedynamic)
invokestatic
Используется для вызова статического метода. (Используется статическое связывание /Static Dispatch)
invokespecial
Используется для прямого вызова методов объекта текущего класса, конструкторов и методов родительского класса (Используется статическое связывание/Static Dispatch).
При этом первым параметром передается ссылка на объект.
Bytecode для конструктора класса Son
invokevirtual
Используется для вызова методов класса, при этом используется динамический поиск какой метод вызывать, основываясь на классе (Dynamic Dispatch). Так как методы могут быть переопределены, то сначала проверяется наличие метода в переданном классе, потом в родительском и так далее. Для поиска в HotSpot используется специальная таблица методов. Подробности можно посмотреть тут. При этом первым параметром передается ссылка на объект.
Немного изменим предыдущий пример
Bytecode для конструктора класса Son
Теперь в строчках 5 и 10 используется invokevirtual.
invokeinterface
Используется для вызова методов интерфейса (Dynamic Dispatch). При этом первым параметром передается ссылка на объект.
На 9 строчке вызывается метод интерфейса
invokedynamic
Вызов динамически-вычисляемых call sites. Сейчас, например, используется в Java для создания объектов для лямбд. Описание, как работает данный opcode, займет не одну статью и лучше всего посмотреть тут
Справочник основных opcode
Полная и единственно достоверная информация находится на сайте Oracle.
aconst_null (0x1)
Положить на стек null
Формат: dload index