|
|
3. Набор команд JVM
Инструкция виртуальной машины Java состоит из однобайтового кода операции,
определяющего действие, которое будет выполнено, и также может содержать операнды,
являющиеся параметрами или данными, которые будут использоваться этой операцией.
Многие инструкции не содержат операндов и состоят только из кода операции. Это
делает код скомпилированных Java-программ компактным.
Без учёта исключений, внутренний цикл интерпретатора виртуальной Java-машины
имеет следующий вид:
Рис.8. Алгоритм интерпретатора байткода.
Количество и размер операндов определяется кодом операции. Если дополнительный
операнд содержит более одного байта, то они хранятся в порядке от старшего к
младшему - старший байт находится по младшему адресу (big-endian). Например,
беззнаковый 16-битный параметр хранится как два байта, его значение вычисляется
как:
(байт1 << 8) | байт2
Рис.9. big-endian.
Рис.10. little-endian.
3.1. Типы данных и набор команд JVM
Большинство инструкций виртуальной машины Java содержат закодированную информацию
о типе, над которым производится операция. Например, команда iload загружает
содержимое локальной переменной, которая должна иметь тип int, на вершину
стека операндов. Инструкция fload делает тоже самое, но с переменной типа
float.
Для большинства типизированных инструкций, тип явно указывается первой буквой
мнемоники команды:
Таблица 2. Буква мнемоники и тип данных команды.
| 'i' |
int |
| 'l' |
long |
| 's' |
short |
| 'b' |
byte |
| 'c' |
char |
| 'f' |
float |
| 'd' |
double |
| 'a' |
reference |
Команды, для которых тип не вызывает сомнения, не содержат буквы типа в своих
мнемониках. Например, команда arraylength всегда работает с объектом, который
является массивом. Некоторые команды, такие как goto (безусловный переход),
не работают с типизированными операндами.
Так как под код операции в формате команд JVM отводится 1 байт, то закодировать
все типы данных в системе команд было бы просто невозможно. Разработчики Java
решили эту проблему следующим образом: команды работают с ограниченным списком
типов, а для работы с оставшимися типами используются команды преобразования типов.
Таблица 3. Команды JVM и поддерживаемые ими типы данных.
| opcode |
byte |
short |
int |
long |
float |
double |
char |
reference |
| Tipush |
bipush |
sipush |
|
|
|
|
|
|
| Tconst |
|
|
iconst |
lconst |
fconst |
dconst |
|
aconst |
| Tload |
|
|
iload |
lload |
fload |
dload |
|
aload |
| Tstore |
|
|
istore |
lstore |
fstore |
dstore |
|
astore |
| Tinc |
|
|
iinc |
|
|
|
|
|
| Taload |
baload |
saload |
iaload |
laload |
faload |
daload |
caload |
aaload |
| Tastore |
bastore |
sastore |
iastore |
lastore |
fastore |
dastore |
castore |
aastore |
| Tadd |
|
|
iadd |
ladd |
fadd |
dadd |
|
|
| Tsub |
|
|
isub |
lsub |
fsub |
dsub |
|
|
| Tmul |
|
|
imul |
lmul |
fmul |
dmul |
|
|
| Tdiv |
|
|
idiv |
ldiv |
fdiv |
ddiv |
|
|
| Trem |
|
|
irem |
lrem |
frem |
drem |
|
|
| Tneg |
|
|
ineg |
lneg |
fneg |
dneg |
|
|
| Tshl |
|
|
ishl |
lshl |
|
|
|
|
| Tshr |
|
|
ishr |
lshr |
|
|
|
|
| Tushr |
|
|
iushr |
lushr |
|
|
|
|
| Tand |
|
|
iand |
land |
|
|
|
|
| Tor |
|
|
ior |
lor |
|
|
|
|
| Txor |
|
|
ixor |
lxor |
|
|
|
|
| i2T |
i2b |
i2s |
|
i2l |
i2f |
i2d |
|
|
| l2T |
|
|
l2i |
|
l2f |
l2d |
|
|
| f2T |
|
|
f2i |
f2l |
|
f2d |
|
|
| d2T |
|
|
d2i |
d2l |
d2f |
|
|
|
| Tcmp |
|
|
|
lcmp |
|
|
|
|
| Tcmpl |
|
|
|
|
fcmpg |
dcmpg |
|
|
| if_TcmpOP |
|
|
if_icmpOP |
|
|
|
|
if_acmpOP |
| Treturn |
|
|
ireturn |
lreturn |
freturn |
dreturn |
|
areturn |
Представленная таблица иллюстрирует поддержку типов в наборе команд виртуальной
машины Java. Конкретная команда с информацией о типе получается путём замены
T в шаблоне команды из левого столбца на букву, соответствующую типу столбца.
Если ячейка пуста, то это означает, что не существует инструкции, которая напрямую
работает с данным типом. Например, существует инструкция load, работающая с
типом int, но нет аналогичной команды, которая работала бы с типом
byte.
Обратите внимание, что большинство инструкций не имеют соответствующих форм для
работы с целыми типами byte, char и short, и ни одна инструкция
не работает с типом boolean. Для поддержки этих типов осуществляется их
расширение до типа int на этапе компиляции или исполения Java-программы.
Причём для типов byte и short используется расширение знаком (рис.11),
а для типов boolean и char - расширение нулём (рис.12). Реально
операции с такими переменными осуществляются при помощи команд, работающих с типом
int.
Рис.11. Расширение знаком.
Рис.12. Расширение нулём.
Некоторые команды JVM, такие как pop и swap, работают со стеком
операндов, не обращая внимания на тип; однако, такие команды ограничены в
использовании только над переменными определённых категорий вычислительных типов.
Соответствие реальных и вычислительных типов виртуальной машины Java представлено
следующей таблицей:
Таблица 4. Реальные и вычислительные типы JVM.
| Реальный тип |
Вычислительный тип |
Количество ячеек стека |
| boolean |
int |
1 |
| byte |
int |
1 |
| char |
int |
1 |
| short |
int |
1 |
| int |
int |
1 |
| float |
float |
1 |
| reference |
reference |
1 |
| returnAddress |
returnAddress |
1 |
| long |
long |
2 |
| double |
double |
2 |
3.2. Команды загрузки и сохранения
Команды загрузки и сохранения выполняют перемещение значений между локальными
переменными и стеком операндов фрейма виртуальной машины Java:
Загрузка локальных переменных в стек операндов: iload,
iload_<n>, lload, lload_<n>, fload, fload_<n>, dload,
dload_<n>, aload, aload_<n>.
Сохранение значения из стека операндов в локальную
переменную: istore, istore_<n>, lstore, lstore_<n>, fstore,
fstore_<n>, dstore, dstore_<n>, astore, astore_<n>.
Загрузка константы в стек операндов: bipush, supush,
ldc, ldc_w, ldc2_w, aconst_null, iconst_m1, iconst_<i>, lconst_<l>,
fconst_<f>, dconst_<d>.
Получение доступа к большему числу локальных переменных,
засчёт использования расширенного индекса, или к большему непосредственному
операнду: wide.
Перечисленные выше мнемоники команд, содержащие в конце букву между двумя
угловыми скобками(например, iload_<n>) обозначают семейства команд
(iload_0, iload_1, iload_2 и iload_3 в случае iload_<n>).
Эти семейства команд являются специальными сокращёнными формами универсальных команд
(в нашем случае iload). Для таких специализированных команд операнд задан
неявно кодом операции, поэтому его не нужно хранить в коде команды и осуществлять
дополнительную выборку при интерпретации. В остальном смысл команды такой же
(iload_0 делает тоже самое, что iload с операндом 0). Буква
между угловыми скобками определяет тип неявного операнда для семейства команд:
<n> - неотрицательное целое; <i> - int; <l> - long;
<f> - float; <d> - double.
3.3. Арифметические и логические команды
Арифметические команды вычисляют результат, который обычно является функцией двух
значений на стеке операндов, помещая результат обратно на стек операндов. Существуют
два основных типа арифметических команд: работающие с целочисленными значениями и
работающие со значениями с плавающей точкой. В системе команд нет прямой поддежки
арифметических и логических операций с типами byte, short, char и
boolean. Работа с этими типами осуществляется при помощи команд, работающих с
типом int.
- Сложение: iadd, ladd, fadd, dadd.
- Вычитание: isub, lsub, fsub, dsub.
- Умножение: imul, lmul, fmul, dmul.
- Деление: idiv, ldiv, fdiv, ddiv..
- Остаток от деления: irem, lrem, frem, drem.
- Отрицание: ineg, lneg, fneg, dneg.
- Сдвиг: ishl, ishr, iushr, lshl, lshr, lushr.
- Поразрядное ИЛИ (дизъюнкция): ior, lor.
- Поразрядное И (конъюнкция): iand, land.
- Поразрядное исключающее ИЛИ (сумма по модулю 2): ixor, lxor.
- Сравнение: dcmpg, dcmpl, fcmpg, fcmpl, lcmp.
Семантика операторов языка программирования Java, работающих с целыми значениями
и значениями с плавающей точкой, напрямую поддерживается семантикой набора команд
виртуальной машины Java.
Виртуальная машина Java не сигнализирует о возникновении переполнения при
выполнении операций над целочисленными типами данных. Единственные целочисленные
операции, которые могут вызвать возникновение исключения - это деление (idiv
и ldiv) и нахождение остатка от деления (irem и lrem), которые
генерируют исключение ArithmeticException, если делитель равен нулю.
3.4. Команды преобразования типов
Команды преобразования типов позволяют выполнять преобразования между численными
типами виртуальной машины Java. Они могут быть использованы для реализации явных
преобразований типов в пользовательском коде или для уменьшения недостатка поддержки
типов в наборе команд JVM.
Виртуальная машина Java напрямую поддерживает следующие расширяющие численные
преобразования типов:
- int в long, float или double (i2l, i2f, i2d)
- long в float или double (l2f, l2d)
- float в double (f2d)
При преобразовании int или long в float, а также long
в double возможна потеря точности. Обратите внимание, что не существует
команд преобразования из типов byte, char, и short в тип int.
Эти преобразования осуществляются неявно на этапе компиляции и при работе
виртуальной машины Java.
JVM также непосредственно осуществляет следующие сужающие преобразования типов:
- int в byte, short или char (i2b, i2s, i2c)
- long в int (l2i)
- float в int или long (f2i, f2l)
- double в int, long или float (d2i, d2l, d2f)
Сужающие преобразования типов могут привести к потере не только точности
представления числа, но также к изменению знака числа. Сужающие преобразования из
типа int или long выполняются простым отбрасывание всех битов числа
кроме N младших битов, где N - количество битов в представлении типа,
являющегося результатом преобразования.
3.5. Команды работы с объектами
Объектами в Java являются как экземпляры классов, так и массивы. Виртуальная
машина Java использует определённые команды для создания экземпляров классов и
массивов:
Создание нового экземпляра класса: new.
Создание нового массива: newarray, anewarray,
multianewarray.
Доступ к полям классов (статические поля) и полям
экземпляров классов (нестатические поля): getfield, putfield, getstatic,
putstatic.
Загрузка элемента массива в стек операндов: baload,
caload, saload, iaload, laload, faload, daload, aaload.
Сохранение элемента массива из стека операндов:
bastore, castore, sastore, iastore, lastore, fastore, dastore, aastore.
Получение длины массива: arraylength.
Проверка свойств экземпляра класса или массива:
instanceof, checkcast.
3.6. Команды управления стеком операндов
JVM предоставляет целый ряд инструкций для прямой работы со стеком операндов:
pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2, swap.
3.7. Команды передачи управления
Команды передачи управления условно или безусловно заставляют JVM продолжать
выполнение с команды, не следующей за командой передачи управления.
Условный переход: ifeq, iflt, ifle, ifne, ifgt, ifge,
ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmpgt, if_icmple,
if_icmpge, if_acmpeq, if_acmpne.
Составной условный переход: tableswitch,
lookupswitch.
Безусловный переход: goto, goto_w, jsr, jsr_w,
ret.
Виртуальная машина Java поддерживает специализированные команды, которые
осуществляют условный переход в зависимости от результата сравнения величин типа
int или reference. Также есть команды для условного перехода в
зависимости от результата проверки на нулевую ссылку.
Условные переходы по результату сравнения величин типов boolean, byte, char,
short осуществляются с помощью команд, работающих с типом int. Условный
переход по результатм сравнения величин типов long, float, double
инициализируется с помощью команды, которая сравнивает величины и генерирует
результат сравнения, который представляет собой переменную типа int.
Последующая команда условного перехода проверяет значение результата сравнения и
выполняет или не выполняет переход.
Все инструкции условных переходов по результату сравнения величин типа int
осуществляют сравнение с учётом знака.
3.8. Команды вызова методов и возврата управления
JVM поддерживает 4 команды для вызова методов:
invokevirtual вызывает метод экземпляра класса,
в зависимости от типа объекта. Это обычный вызов метода в языке программирования
Java.
invokeinterface вызывает метод, который
реализуется интерфейсом. Осуществляется поиск методов, реализованных определённым
объектом, чтобы найти подходящий метод.
invokespecial вызывает метод экземпляра класса,
требующий специальной обработки, например метод инициализации экземпляра класса,
private-метод или метод суперкласса.
invokestatic вызывает метод класса (статический
метод).
Команды возврата из методов различаются по типу возвращаемого значения:
ireturn (используется для возврата значений типов boolean, byte, char,
short или int), lreturn, freturn, dreturn и areturn.
Команда return используется для возврата из методов, которые объявлены как
void, методов инициализации экземпляра класса, методов инициализации класса
или интерфейса.
3.9. Генерация исключений
Исключение может быть сгенерированно программно с помощью команды athrow.
Исключения могут также быть сгенерированы различными командами JVM, если они
определяют ненормальные условия.
Наверх | Дальше
|
|