Байт-код Java — это набор инструкций виртуальной машины Java (JVM), языка, на котором компилируется исходный код Java и других совместимых с JVM программ . [1] Каждая инструкция представлена одним байтом , отсюда и название байт-код , что делает его компактной формой данных . [2]
Благодаря природе байт-кода программа на Java-байт-коде может быть запущена на любой машине с совместимой JVM; без длительного процесса компиляции из исходного кода.
Байт-код Java используется во время выполнения и либо интерпретируется JVM, либо компилируется в машинный код с помощью JIT -компиляции и запускается как собственное приложение.
Поскольку байт-код Java разработан для обеспечения кроссплатформенной совместимости и безопасности, приложение с байт-кодом Java имеет тенденцию работать одинаково на различных аппаратных и программных конфигурациях. [3]
В общем, программисту Java не нужно понимать байт-код Java или даже знать о нем. Однако, как предполагается в журнале IBM developerWorks, «Понимание байт-кода и того, какой байт-код, скорее всего, будет сгенерирован компилятором Java , помогает программисту Java так же, как знание ассемблера помогает программисту C или C++ ». [4]
Байт-код содержит различные типы инструкций, включая обработку данных, передачу управления, создание и обработку объектов, а также вызов методов, которые являются неотъемлемой частью объектно-ориентированной модели программирования Java. [1]
JVM — это и стековая , и регистровая машина . Каждый фрейм для вызова метода имеет «стек операндов» и массив «локальных переменных». [5] : 2.6 [2] Стек операндов используется для операндов для вычислений и для получения возвращаемого значения вызванного метода, в то время как локальные переменные служат той же цели, что и регистры , а также используются для передачи аргументов метода. Максимальный размер стека операндов и массива локальных переменных, вычисляемый компилятором, является частью атрибутов каждого метода. [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 для инструкций load
и store
указывает индекс в массиве локальных переменных для загрузки или сохранения. Инструкция aload_0
помещает объект в локальной переменной 0 в стек (обычно это this
объект). istore_1
сохраняет целое число наверху стека в локальной переменной 1. Для локальных переменных после 3 суффикс отбрасывается, и необходимо использовать операнды.
Рассмотрим следующий код Java:
внешний : for ( int i = 2 ; i < 1000 ; i ++ ) { for ( int j = 2 ; j < i ; j ++ ) { if ( i % j == 0 ) continue external ; } System.out.println ( i ) ; }
Компилятор 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 : ifne 25 22 : goto 38 25 : iinc 2, 1 28 : goto 11 31 : getstatic #84; // Поле java/lang/System.out : Ljava/io/PrintStream; 34 : iload_1 35 : invokevirtual #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 Compiler for Java (GCJ). Некоторые процессоры могут выполнять байт-код Java нативно. Такие процессоры называются процессорами Java .
Виртуальная машина Java обеспечивает некоторую поддержку динамически типизированных языков . Большая часть существующего набора инструкций JVM является статически типизированной — в том смысле, что сигнатуры вызовов методов проверяются на тип во время компиляции , без механизма, позволяющего отложить это решение до времени выполнения или выбрать диспетчеризацию метода с помощью альтернативного подхода. [12]
JSR 292 ( Поддержка динамически типизированных языков на платформе Java ) [13] добавил новую invokedynamic
инструкцию на уровне JVM, чтобы разрешить вызов метода, полагаясь на динамическую проверку типов (вместо существующей invokevirtual
инструкции со статической проверкой типов). Da Vinci Machine — это прототип реализации виртуальной машины, которая размещает расширения JVM, направленные на поддержку динамических языков. Все JVM, поддерживающие JSE 7, также включают invokedynamic
код операции.