Java байт код это

Введение в байт-код Java, о котором вы не знали

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

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

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

Если вы слышали, как Java бредет о независимости программ Java от платформы в какой-то момент вашей программистской жизни, вы должны поблагодарить за это байт-код Java.

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

Как генерируется байт-код?

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

Читайте также:  Pycharm python integrated tools

Как вы можете просмотреть байт-код Java?

Если вы пытались открыть файл .class, вы должны знать по опыту, что это невозможно, если вы не используете декомпилятор. Однако при включенном декомпиляторе вы на самом деле видите не байт-код, а код Java, в который декомпилятор ретранслирует байт-код.

Если вы действительно хотите увидеть сам байт-код, самый простой способ сделать это — использовать командную строку.

Вы можете запустить следующую команду, чтобы увидеть фактический байт-код файла класса.

javap -c -p -v [path to the .class file]
  • -c используется для дизассемблирования класса Java. Если вы хотите увидеть фактический байт-код, используйте этот флаг.
  • -p используется для раскрытия закрытых членов класса.
  • -v используется для просмотра подробной информации, такой как размер стека и постоянный пул.

Немного о том, как работает JVM

Прежде чем углубляться в байт-код Java, мы должны немного понять, как JVM обрабатывает байт-код.

Методы — одна из самых важных частей кода Java для JVM. Фактически, среда выполнения программы Java представляет собой набор методов, вызываемых JVM. Он создает нечто, называемое фреймом, для каждого вызываемого метода и помещает созданный фрейм поверх стека текущего потока для выполнения.

Фрейм состоит из локальной среды, необходимой для его выполнения. В основном он содержит массив локальных переменных и стек операндов. Посмотрим, что из себя представляет каждый из них.

Массив локальных переменных

Массив локальных переменных, как следует из названия, используется для хранения локальных переменных, используемых в методе. Кроме того, он также хранит аргументы, принятые методом.

В массиве локальных переменных с нулевым индексом первые индексы используются для хранения аргументов метода. После того, как они сохранены, другие локальные переменные сохраняются в массиве. Если метод является методом экземпляра, а не статическим, нулевой индекс зарезервирован для хранения ссылки this , которая указывает на экземпляр объекта, который используется для вызова метода.

Давайте определим два метода: один статический и один экземплярный, но во всем остальном они похожи.

Массивы локальных переменных этих двух будут выглядеть следующим образом.

Стек операндов

Стек операндов — это рабочая область внутри фрейма метода. Поскольку это стек, как следует из названия, вы можете вставлять и извлекать значения только из вершины стека операндов. Большинство инструкций байт-кода, принадлежащих к определенному методу, либо задействованы в отправке значений в стек, либо в извлечении значений из стека для их обработки.

Инструкция байт-кода load и ее расширения используются для помещения значения, хранящегося в массиве переменных, в стек. Инструкция store используется для извлечения значений из стека и сохранения их в массиве переменных. Кроме того, есть другие инструкции, которые извлекают значения из стека для их обработки.

Примерами таких сценариев являются команда add , которая извлекает два самых верхних значения из стека и складывает их вместе, и инструкции вызова метода, которые извлекают самые верхние значения (число зависит от количества параметры, принятые методом) из стека, чтобы передать их в качестве аргументов методу. Если эти команды имеют результирующие значения, они возвращаются в стек.

aload_0 //push the reference to non-primitive data value at index 0 of the variable array iload_2 //push the int value at index 4 of the variable array iconst_3 //push the int 3 on to the stack iadd //add the two top-most int values on the stack istore_3 //pop the result of the add operation and store at index 6 of the variable array

Давайте посмотрим байт-код

Я написал простой класс Java, чтобы мы могли видеть его байт-код.

Теперь запустите, давайте скомпилируем класс с помощью команды javac и просмотрите байт-код с помощью команды javap . Без подробностей результат выглядит так.

Когда вы посмотрите на эти инструкции по байт-коду, вы обнаружите несколько знакомых, включая команды load и const. Остальное, однако, может немного сбить вас с толку.

Разбор байт-кода

Это не так страшно, как кажется. Давайте попробуем разобрать байт-код SimpleClass один за другим. Начнем с простейшего метода isEven .

private boolean isEven(int num)

private boolean isEven(int); Code: 0: iload_1 1: iconst_2 2: irem 3: ifne 10 6: iconst_1 7: goto 11 10: iconst_0 11: ireturn
  1. Сначала инструкция iload_1 помещает значение индекса 1 массива локальных переменных в стек операндов. Поскольку метод isEven является методом экземпляра, ссылка на this сохраняется в индексе 0. Тогда мы можем легко понять, что значение, хранящееся в индексе 1, фактически является принятым значением параметра int.
  2. iconst_2 помещает значение int 2 в верхнюю часть стека операндов.
  3. irem инструкция используется для нахождения остатка от деления между двумя числами. Это инструкция, которая представляет логику оператора%. Он выталкивает 2 самых верхних значения в стек и помещает результат обратно в стек.
  4. Команда ifne указывает JVM перейти к инструкции с заданным смещением, в данном случае 10, если значение, обработанное командой, не равно 0. Команда выталкивает верхний элемент стека. чтобы реализовать эту логику на. Если переданное число было четным, верхним элементом будет 0, и в этом случае JVM получает указание перейти к инструкции с индексом 6. Однако, если значение стека не равно нулю, что происходит, когда число не четное, JVM перемещается к инструкции с индексом 10.
  5. iconst_1 помещает в стек значение int 1. Это происходит только в том случае, если результат irem равен 1. Здесь 1 используется для представления логического значения true .
  6. goto указывает JVM перейти к инструкции, указанной в смещении, которое в данном случае равно 11. goto инструкция используется для перехода с одного места в таблице инструкций на другое.
  7. iconst_0 помещает значение 0 в стек. Эта инструкция используется, когда условие if оказывается false . Переданное значение 0 действует как логическое значение false. Инструкции в 3, 6, 7 обрабатывают случай, когда условие if истинно.
  8. ireturn возвращает значение типа int в верхней части стека.

Еще одна важная вещь, на которую следует обратить внимание, — это индексы, данные инструкциям байт-кода. Вы можете видеть, что они не увеличиваются на единицу для каждой новой инструкции.

Каждое число перед инструкцией указывает индекс ее начального байта. И каждый байт-код состоит из однобайтового кода операции, за которым следует ноль или более операндов.

Коды операций — это такие команды, как iload , iconst и т. Д. В зависимости от размера операндов размер байт-кода может варьироваться от одного байта до большего. Следовательно, вы видите пробелы в индексах таблицы инструкций. Здесь единственная двухбайтовая инструкция ifne .

В байт-коде нашего SimpleClass.class файла мы можем видеть другие инструкции, такие как invokespecial , invokeinterface и invokestatic , которые являются инструкциями по вызову методов. Мы поговорим о них в следующей части этой серии статей.

Между тем, если вы хотите понять, что означает каждая инструкция в вашем байт-коде, вы можете обратиться к этой отличной статье в Википедии, в которой содержится список инструкций.

Заключение

Надеюсь, вам удалось много узнать о том, как работает байт-код Java, в этой статье. В следующих статьях этой серии мы обсудим более сложные инструкции по вызову методов и то, как JVM обрабатывает вызов методов.

Обладая более четкими знаниями байт-кода Java и JVM, вы сможете писать лучший код. Вы даже можете поэкспериментировать с манипулированием самим байт-кодом во время выполнения программы с помощью таких библиотек, как ASM. Но, как я уже сказал, мы поговорим о них позже.

Источник

Оцените статью