Байт-код Java — это набор инструкций виртуальной машины Java (JVM), который имеет решающее значение для выполнения программ, написанных на языке Java и других JVM-совместимых языках. [1] Каждая операция с байт-кодом в JVM представлена одним байтом, отсюда и название « байт-код », что делает ее компактной формой инструкции. [2] Эта промежуточная форма позволяет программам Java быть независимыми от платформы, поскольку они компилируются не в собственный машинный код, а в универсальный исполняемый формат в различных реализациях JVM.
JVM интерпретирует этот байт-код или на лету компилирует его в собственный машинный код с помощью JIT-компилятора, повышая производительность Java-приложений. Конструкция байт-кода Java нацелена на высокую степень межплатформенной совместимости и безопасности, выполняемую в среде, контролируемой JVM. Эта архитектура позволяет приложениям Java согласованно работать в различных конфигурациях оборудования и программного обеспечения. [3] Хотя Java-программисты обычно не взаимодействуют напрямую с байт-кодом, понимание его структуры и выполнения может оказаться полезным для целей оптимизации и отладки.
В JVM байт-код Java работает как набор инструкций как для стековой машины, так и для регистровой машины, используя стек операндов и локальные переменные для выполнения операций. [2] Байт-код включает в себя различные типы инструкций, включая манипулирование данными, передачу управления, создание и манипулирование объектами, а также вызов методов, которые являются неотъемлемой частью модели объектно-ориентированного программирования Java. [1]
Java- программисту вообще не обязательно знать или понимать байт-код Java. Однако, как говорится в журнале IBM DeveloperWorks: «Понимание байт-кода и того, какой байт-код может быть сгенерирован компилятором Java, помогает программисту Java так же, как знание ассемблера помогает программисту C или C++ ». [4]
JVM является одновременно стековой машиной и регистровой машиной . Каждый кадр вызова метода имеет «стек операндов» и массив «локальных переменных». [5] : 2.6 Стек операндов используется для операндов вычислений и для получения возвращаемого значения вызываемого метода, в то время как локальные переменные служат той же цели, что и регистры , а также используются для передачи аргументов метода. Максимальный размер стека операндов и массива локальных переменных, вычисленный компилятором, является частью атрибутов каждого метода. [5] : 4.7.3 Каждый из них может иметь независимый размер от 0 до 65535 значений, где каждое значение составляет 32 бита. long
и double
типы, которые имеют размер 64 бита, занимают две последовательные локальные переменные [5] : 2.6.1 (которые не обязательно должны быть выровнены по 64 битам в массиве локальных переменных) или одно значение в стеке операндов (но считаются как две единицы в глубине стека). [5] : 2.6.2
Каждый байт-код состоит из одного байта, представляющего код операции , а также нуля или более байтов для операндов. [5] : 2,11
Из 256 возможных опкодов длиной в байт по состоянию на 2015 год [обновлять]202 используются (~79%), 51 зарезервированы для использования в будущем (~20%), а 3 инструкции (~1%) постоянно зарезервированы для реализаций JVM для использовать. [5] : 6.2 Два из них ( impdep1
и impdep2
) предназначены для создания ловушек для программного и аппаратного обеспечения, специфичного для реализации, соответственно. Третий используется отладчиками для реализации точек останова.
Инструкции делятся на несколько широких групп:
aload_0
, istore
)ladd
, fcmpl
)i2b
, d2i
)new
, putfield
)swap
, dup2
)ifeq
, goto
)invokespecial
, areturn
)Есть также несколько инструкций для ряда более специализированных задач, таких как выдача исключений, синхронизация и т. д.
Многие инструкции имеют префиксы и/или суффиксы, относящиеся к типам операндов, с которыми они работают. [5] : 2.11.1 К ним относятся следующие:
Например, iadd
добавится два целых числа и dadd
два двойных числа. Инструкции const
, load
, и store
также могут иметь суффикс вида , где n — число от 0 до 3 для и . Максимальное n для зависит от типа._n
load
store
const
Инструкции const
помещают значение указанного типа в стек. Например, iconst_5
в стек будет помещено целое число (32-битное значение) со значением 5, а в стек dconst_1
будет помещено двойное значение (64-битное значение с плавающей запятой) со значением 1. Существует также aconst_null
, который отправляет null
ссылку. n для инструкций и указывает индекс в массиве локальных переменных, из которого выполняется загрузка или сохранение . Инструкция помещает объект из локальной переменной 0 в стек (обычно это объект ). сохраняет целое число на вершине стека в локальную переменную 1. Для локальных переменных после 3 суффикс отбрасывается и необходимо использовать операнды.load
store
aload_0
this
istore_1
Рассмотрим следующий код Java:
внешний : for ( int i = 2 ; я < 1000 ; я ++ ) { for ( int j = 2 ; j < i ; j ++ ) { if ( i % j == 0 ) продолжить внешний ; } Система . вне . печатьln ( я ); }
Компилятор Java может преобразовать приведенный выше код Java в байт-код следующим образом, предполагая, что приведенное выше было помещено в метод:
0 : iconst_2 1 : istore_1 2 : iload_1 3 : sipush 1000 6 : if_icmpge 44 9 : iconst_2 10 : istore_2 11 : iload_2 12 : iload_1 13 : if_icmpge 31 16 : iload_1 17 : iload_2 18 : irem 19 : если не 25 22 : перейти к 38 25 : iinc 2, 1 28 : перейти к 11 31 : getstatic #84; // Поле java/lang/System.out : Ljava/io/PrintStream; 34 : iload_1 35 : вызвать виртуальный #85 ; // Метод java/io/PrintStream.println:(I)V 38 : iinc 1, 1 41 : переход к 2 44 : возврат
Наиболее распространенным языком, предназначенным для виртуальной машины Java для создания байт-кода Java, является Java. Первоначально существовал только один компилятор — компилятор javac от Sun Microsystems , который компилирует исходный код Java в байт-код Java; но поскольку все спецификации байт-кода Java теперь доступны, другие стороны предоставили компиляторы, которые создают байт-код Java. Примеры других компиляторов включают:
Некоторые проекты предоставляют ассемблеры Java, позволяющие писать байт-код Java вручную. Ассемблерный код также может быть сгенерирован машиной, например, компилятором, предназначенным для виртуальной машины Java . Известные ассемблеры Java включают:
Другие разработали компиляторы для разных языков программирования для виртуальной машины Java, например:
Сегодня доступно несколько виртуальных машин Java для выполнения байт-кода Java, как бесплатных, так и коммерческих продуктов. Если выполнение байт-кода на виртуальной машине нежелательно, разработчик может также скомпилировать исходный код Java или байт-код непосредственно в машинный код с помощью таких инструментов, как компилятор GNU для Java (GCJ). Некоторые процессоры могут самостоятельно выполнять байт-код Java. Такие процессоры называются процессорами Java .
Виртуальная машина Java обеспечивает некоторую поддержку динамически типизированных языков . Большая часть существующего набора инструкций JVM статически типизирована - в том смысле, что сигнатуры вызовов методов проверяются по типу во время компиляции , без механизма, позволяющего отложить это решение до времени выполнения или выбрать отправку метода альтернативным подходом. [12]
В JSR 292 ( Поддержка динамически типизированных языков на платформе Java ) [13] добавлена новая invokedynamic
инструкция на уровне JVM, позволяющая вызывать метод на основе динамической проверки типов (вместо существующей invokevirtual
инструкции со статической проверкой типов). Da Vinci Machine — это прототип реализации виртуальной машины, на котором размещены расширения JVM, предназначенные для поддержки динамических языков. Все JVM, поддерживающие JSE 7, также включают invokedynamic
код операции.