Что будет и кому может быть интересно
Внимание, статья содержит довольно много картинок и получилась довольно тяжелой и объемной
Как и многие базовые вещи, на habr уже были статьи о bytecode (раз, два), основные же отличия данной статьи — в попытке визуализировать, что происходит внутри, и краткий справочник инструкций (может кому пригодится), многие с примерами использования.
В данной статье будут рассмотрены только основы Java Bytecode. Если вы уже знакомы с его основами, статья вряд ли будет вам интересна, так как практически все можно найти в документации.
В данной статье не рассмотрены многие темы (например, фреймы, многие атрибуты), иначе она бы получилась еще больше
Зачем знать что-то о Bytecode
Тема bytecode довольно скучная и в реальной работе среднестатистического программиста практически не используется.
Так почему стоит знать про основы bytecode:
![](https://habrastorage.org/getpro/habr/upload_files/bc8/d57/54a/bc8d5754a255d2aac8567cf9c0e6f4b9.jpeg)
-
Потому что с этим работает Java Machine и хочется понимать, что лежит в основе
-
Потому что многие современные фреймворки что-то тихо делают на уровне bytecode и часто могут что-то там сломать (привет, Lombok)
-
Потому что просто стало скучно 🙂
Как читать .class файл
Для начала вспомним, как создать .class файл. Для этого воспользуемся
javac File.java
Создается .class файл. Формат class файла — бинарный, файл содержит все, что нужно для выполнения программы JVM. При этом действует правило — 1 класс на 1 файл. В случае вложенных классов создаются дополнительные class файлы.
Если открыть любой class файл в hex редакторе, то файл начинается с «магических байт» — CAFEBABE, а дальше следует полезное содержимое файла.
Для того чтобы просмотреть содержимое, можно воспользоваться стандартной утилитой
javap File
javap может принимать много параметров. Давайте рассмотрим основные из них.
Смотреть будем на стандартном классе java.lang.Object
Без параметров
Выводится только основная информация по классам, методам и полям (приватные поля и методы не показываются)
javap java.lang.Object
Compiled from "Object.java" public class java.lang.Object { public java.lang.Object(); public final native java.lang.Class<?> getClass(); public native int hashCode(); public boolean equals(java.lang.Object); protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException; public java.lang.String toString(); public final native void notify(); public final native void notifyAll(); public final native void wait(long) throws java.lang.InterruptedException; public final void wait(long, int) throws java.lang.InterruptedException; public final void wait() throws java.lang.InterruptedException; protected void finalize() throws java.lang.Throwable; static {}; }
-p
Показываются также приватные поля и методы
javap -p java.lang.Object
Compiled from "Object.java" public class java.lang.Object { public java.lang.Object(); private static native void registerNatives(); public final native java.lang.Class<?> getClass(); public native int hashCode(); public boolean equals(java.lang.Object); protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException; public java.lang.String toString(); public final native void notify(); public final native void notifyAll(); public final native void wait(long) throws java.lang.InterruptedException; public final void wait(long, int) throws java.lang.InterruptedException; public final void wait() throws java.lang.InterruptedException; protected void finalize() throws java.lang.Throwable; static {}; }
-v
Показываются подробную информацию (verbose), такую, как размер стека и аргументов, версии и т.д.
javap -v java.lang.Object
Результат вывода
Classfile jar:file{путь к файлу} Last modified 15.12.2018; size 1497 bytes MD5 checksum 074ebc688a81170b8740f1158648a3c7 Compiled from "Object.java" public class java.lang.Object minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Integer 999999 #2 = String #16 // @ #3 = String #38 // nanosecond timeout value out of range #4 = String #42 // timeout value is negative #5 = Utf8 ()I #6 = Utf8 ()Ljava/lang/Object; #7 = Utf8 ()Ljava/lang/String; #8 = Utf8 ()V #9 = Utf8 (I)Ljava/lang/String; #10 = Utf8 (J)V #11 = Utf8 (JI)V #12 = Utf8 (Ljava/lang/Object;)Z #13 = Utf8 (Ljava/lang/String;)V #14 = Utf8 <clinit> #15 = Utf8 <init> #16 = Utf8 @ #17 = Utf8 Code #18 = Utf8 Exceptions #19 = Utf8 LineNumberTable #20 = Utf8 Signature #21 = Utf8 SourceFile #22 = Utf8 StackMapTable #23 = Utf8 append #24 = Utf8 clone #25 = Utf8 equals #26 = Utf8 finalize #27 = Utf8 getClass #28 = Utf8 getName #29 = Utf8 hashCode #30 = Utf8 java/lang/Class #31 = Utf8 java/lang/CloneNotSupportedException #32 = Utf8 java/lang/IllegalArgumentException #33 = Utf8 java/lang/Integer #34 = Utf8 java/lang/InterruptedException #35 = Utf8 java/lang/Object #36 = Utf8 java/lang/StringBuilder #37 = Utf8 java/lang/Throwable #38 = Utf8 nanosecond timeout value out of range #39 = Utf8 notify #40 = Utf8 notifyAll #41 = Utf8 registerNatives #42 = Utf8 timeout value is negative #43 = Utf8 toHexString #44 = Utf8 toString #45 = Utf8 wait #46 = Class #30 // java/lang/Class #47 = Class #31 // java/lang/CloneNotSupportedException #48 = Class #32 // java/lang/IllegalArgumentException #49 = Class #33 // java/lang/Integer #50 = Class #34 // java/lang/InterruptedException #51 = Class #35 // java/lang/Object #52 = Class #36 // java/lang/StringBuilder #53 = Class #37 // java/lang/Throwable #54 = Utf8 ()Ljava/lang/Class; #55 = Utf8 ()Ljava/lang/Class<*>; #56 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #57 = NameAndType #29:#5 // hashCode:()I #58 = NameAndType #15:#8 // "<init>":()V #59 = NameAndType #41:#8 // registerNatives:()V #60 = NameAndType #45:#10 // wait:(J)V #61 = NameAndType #27:#54 // getClass:()Ljava/lang/Class; #62 = NameAndType #28:#7 // getName:()Ljava/lang/String; #63 = NameAndType #44:#7 // toString:()Ljava/lang/String; #64 = NameAndType #43:#9 // toHexString:(I)Ljava/lang/String; #65 = NameAndType #15:#13 // "<init>":(Ljava/lang/String;)V #66 = NameAndType #23:#56 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #67 = Methodref #46.#62 // java/lang/Class.getName:()Ljava/lang/String; #68 = Methodref #48.#65 // java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V #69 = Methodref #49.#64 // java/lang/Integer.toHexString:(I)Ljava/lang/String; #70 = Methodref #51.#57 // java/lang/Object.hashCode:()I #71 = Methodref #51.#59 // java/lang/Object.registerNatives:()V #72 = Methodref #51.#60 // java/lang/Object.wait:(J)V #73 = Methodref #51.#61 // java/lang/Object.getClass:()Ljava/lang/Class; #74 = Methodref #52.#58 // java/lang/StringBuilder."<init>":()V #75 = Methodref #52.#63 // java/lang/StringBuilder.toString:()Ljava/lang/String; #76 = Methodref #52.#66 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #77 = Utf8 Object.java { public java.lang.Object(); descriptor: ()V flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 37: 0 public final native java.lang.Class<?> getClass(); descriptor: ()Ljava/lang/Class; flags: ACC_PUBLIC, ACC_FINAL, ACC_NATIVE Signature: #55 // ()Ljava/lang/Class<*>; public native int hashCode(); descriptor: ()I flags: ACC_PUBLIC, ACC_NATIVE public boolean equals(java.lang.Object); descriptor: (Ljava/lang/Object;)Z flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: if_acmpne 9 5: iconst_1 6: goto 10 9: iconst_0 10: ireturn StackMapTable: number_of_entries = 2 frame_type = 9 /* same */ frame_type = 64 /* same_locals_1_stack_item */ stack = [ int ] LineNumberTable: line 149: 0 protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException; descriptor: ()Ljava/lang/Object; flags: ACC_PROTECTED, ACC_NATIVE Exceptions: throws java.lang.CloneNotSupportedException public java.lang.String toString(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: new #52 // class java/lang/StringBuilder 3: dup 4: invokespecial #74 // Method java/lang/StringBuilder."<init>":()V 7: aload_0 8: invokevirtual #73 // Method getClass:()Ljava/lang/Class; 11: invokevirtual #67 // Method java/lang/Class.getName:()Ljava/lang/String; 14: invokevirtual #76 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 17: ldc #2 // String @ 19: invokevirtual #76 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: aload_0 23: invokevirtual #70 // Method hashCode:()I 26: invokestatic #69 // Method java/lang/Integer.toHexString:(I)Ljava/lang/String; 29: invokevirtual #76 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: invokevirtual #75 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 35: areturn LineNumberTable: line 236: 0 public final native void notify(); descriptor: ()V flags: ACC_PUBLIC, ACC_FINAL, ACC_NATIVE public final native void notifyAll(); descriptor: ()V flags: ACC_PUBLIC, ACC_FINAL, ACC_NATIVE public final native void wait(long) throws java.lang.InterruptedException; descriptor: (J)V flags: ACC_PUBLIC, ACC_FINAL, ACC_NATIVE Exceptions: throws java.lang.InterruptedException public final void wait(long, int) throws java.lang.InterruptedException; descriptor: (JI)V flags: ACC_PUBLIC, ACC_FINAL Code: stack=4, locals=4, args_size=3 0: lload_1 1: lconst_0 2: lcmp 3: ifge 16 6: new #48 // class java/lang/IllegalArgumentException 9: dup 10: ldc #4 // String timeout value is negative 12: invokespecial #68 // Method java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V 15: athrow 16: iload_3 17: iflt 26 20: iload_3 21: ldc #1 // int 999999 23: if_icmple 36 26: new #48 // class java/lang/IllegalArgumentException 29: dup 30: ldc #3 // String nanosecond timeout value out of range 32: invokespecial #68 // Method java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V 35: athrow 36: iload_3 37: ifle 44 40: lload_1 41: lconst_1 42: ladd 43: lstore_1 44: aload_0 45: lload_1 46: invokevirtual #72 // Method wait:(J)V 49: return StackMapTable: number_of_entries = 4 frame_type = 16 /* same */ frame_type = 9 /* same */ frame_type = 9 /* same */ frame_type = 7 /* same */ LineNumberTable: line 447: 0 line 448: 6 line 451: 16 line 452: 26 line 456: 36 line 457: 40 line 460: 44 line 461: 49 Exceptions: throws java.lang.InterruptedException public final void wait() throws java.lang.InterruptedException; descriptor: ()V flags: ACC_PUBLIC, ACC_FINAL Code: stack=3, locals=1, args_size=1 0: aload_0 1: lconst_0 2: invokevirtual #72 // Method wait:(J)V 5: return LineNumberTable: line 502: 0 line 503: 5 Exceptions: throws java.lang.InterruptedException protected void finalize() throws java.lang.Throwable; descriptor: ()V flags: ACC_PROTECTED Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 555: 0 Exceptions: throws java.lang.Throwable static {}; descriptor: ()V flags: ACC_STATIC Code: stack=0, locals=0, args_size=0 0: invokestatic #71 // Method registerNatives:()V 3: return LineNumberTable: line 41: 0 line 42: 3 } SourceFile: "Object.java"
В IDEA имеет встроенные средства для просмотра:
![Просмотр для Java Просмотр для Java](https://habrastorage.org/getpro/habr/upload_files/3c3/61c/f76/3c361cf7673358cfcc15311ee14fe7c9.jpg)
![Просмотр для Kotlin Просмотр для Kotlin](https://habrastorage.org/getpro/habr/upload_files/0f0/0e9/1c3/0f00e91c306349fd4b012e43b3d4fc0f.jpg)
Что содержит .class файл
Структура .class файла (документация):
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
u1
, u2
, and u4
— размер полей
cp_info
, field_info
, method_info
, attribute_info
— специальные таблицы, о которых рассказывается ниже
-
magic — магическая константа (0xCAFEBABE), мы о ней уже говорили.
-
minor_version, major_version — версия формата .class файла (смотреть ниже)
-
constant_pool_count и constant_pool — длина пула констант и сам пул констант ( Пул констант — таблица для записи различный текстовых констант, имен интерфейсов, классов, полей и другие константы, на которые в дальнейшем будут ссылки в процессе выполнения, раздел
Constant pool
при выводе javap -v) -
access_flags — набор флагов (public, abstract, enum и т.д.)
-
this_class — ссылка на пул констант, которая определяет данный класс
-
super_class — ссылка на пул констант, которая определяет родительский класс
-
interfaces_count и interfaces — количество интерфейсов, которые реализуют класс и ссылки на пул констант для этих интерфейсов
-
fields_count и fields — информация по полям
-
methods_count и methods — информация по методам
-
attributes_count и attributes — информация по атрибутам
Версии class файлов
Довольно часто можно увидеть ошибку, если запускать на более ранней версии jvm: Exception in thread "main" java.lang.UnsupportedClassVersionError: ... has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
. Здесь 55.0 и 52.0 — версии class файлов.
Чтобы понять, какие версии class файлов какая jvm поддерживает, можно воспользоваться таблицей из документации:
Версия Java SE |
Дата выхода |
Major |
Поддерживаемые major версии |
---|---|---|---|
1.0.2 |
Май 1996 |
45 |
45 |
1.1 |
Февраль 1997 |
45 |
45 |
1.2 |
Декабрь 1998 |
46 |
45 .. 46 |
1.3 |
Май 2000 |
47 |
45 .. 47 |
1.4 |
Февраль 2002 |
48 |
45 .. 48 |
5.0 |
Сентябрь 2004 |
49 |
45 .. 49 |
6 |
Декабрь 2006 |
50 |
45 .. 50 |
7 |
Июль 2011 |
51 |
45 .. 51 |
8 |
Март 2014 |
52 |
45 .. 52 |
9 |
Сентябрь 2017 |
53 |
45 .. 53 |
10 |
Март 2018 |
54 |
45 .. 54 |
11 |
Сентябрь 2018 |
55 |
45 .. 55 |
12 |
Март 2019 |
56 |
45 .. 56 |
13 |
Сентябрь 2019 |
57 |
45 .. 57 |
14 |
Март 2020 |
58 |
45 .. 58 |
15 |
Сентябрь 2020 |
59 |
45 .. 59 |
16 |
Март 2021 |
60 |
45 .. 60 |
Для версии Java, начиная с 5, работает формула Major-44=Версия Java
access_flags
Flag Name (Имя флага) |
Value (Значение) |
Interpretation (Интерпретация) |
---|---|---|
|
0x0001 |
public, виден за пределами пакета |
|
0x0010 |
|
|
0x0020 |
Обработка методов супер-класса когда вызывается инструкция invokespecial. (ссылка на более подробное объяснение) |
|
0x0200 |
Интерфейс, не класс |
|
0x0400 |
Абстрактный, не может быть наследован |
|
0x1000 |
Синтетический, не из кода |
|
0x2000 |
Аннотация |
|
0x4000 |
Enum |
|
0x8000 |
Модуль, не класс или интерфейс |
Внутрь Bytecode
Рассмотрим первый простой файл:
public class SayHello { public static void main(String[] args) { System.out.println("Hello, world!"); } }
Выполним последовательно:
javac SayHello.java
javap -v -p SayHello
Результат выполнения
Classfile {путь до файла} Last modified Jul 22, 2021; size 423 bytes MD5 checksum b20c6d9cd0d9345e3cc8b95a0c0cda71 Compiled from "SayHello.java" public class SayHello minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // SayHello super_class: #6 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello, world! #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // SayHello #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 SayHello.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello, world! #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 SayHello #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V { public SayHello(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello, world! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 } SourceFile: "SayHello.java"
minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #5 // SayHello super_class: #6 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1
Версия 55.0, что соответствует версии Java 11 (55-44=11)
access_flags
— ACC_PUBLIC, ACC_SUPER, так как класс публичный и при создании требуется вызвать метод суперкласса.
this_class
— ссылка на имя класса (SayHello
)
super_class
— ссылка на родительский класс (java/lang/Object
)
interfaces: 0, fields: 0, methods: 2, attributes: 1
— количество интерфейсов, полей, методов и атрибутов
Constant pool
: — пул констант, обратите внимание, что здесь присутствуют ссылки на все методы и классы, используемые в коде, а также текст, который выводим (SayHello)
SourceFile
— один из аттрибутов
Рассмотрим теперь файл с каким-нибудь полем :
public class SayHello { public final String hello = "Hello"; }
Часть bytecode, касающаяся поля:
public final java.lang.String hello; descriptor: Ljava/lang/String; flags: (0x0011) ACC_PUBLIC, ACC_FINAL ConstantValue: String Hello
Для полей приведены флаги доступа, имя, описание (сигнатура поля) и атрибуты (здесь, ConstantValue)
Сигнатуры (descriptor) для полей имеют следующий формат:
-
B — byte
-
C — char
-
D — double
-
F — float
-
I — int
-
J — long
-
S — short
-
Z — boolean
-
Lимя_класса — ccылочный тип (как в примере — Ljava/lang/String)
-
[ — массив, например long[] будет [J
Описание структуры метода
Рассмотрим простой класс:
public class Main { public static void main(String[] args) { int b = 1; int a = b + 2; System.out.println(a); } }
Выполним компиляцию с сохранением названий переменных (чтобы было легче анализировать — флаг -g
):
javac -g Main.java
javap -v -p Main
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: iconst_1 1: istore_1 2: iload_1 3: iconst_2 4: iadd 5: istore_2 6: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 9: iload_2 10: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 13: return LineNumberTable: line 4: 0 line 5: 2 line 6: 6 line 7: 13 LocalVariableTable: Start Length Slot Name Signature 0 14 0 args [Ljava/lang/String; 2 12 1 b I 6 8 2 a I
Документацию можно посмотреть тут.
Descriptor — что принимает и возвращает метод. Он приводится в формате ([param1[param2[...]]])returnValue
. Для приведенного выше метода — ([Ljava/lang/String;)V
.
Пример, (BB)I
это int methodName(byte b1, byte b2)
.
V
соответствует void. Конструктор: имя = <init>
, тип=(..)V
. Инициализатор класса: имя=<clinit>
, тип=()V
Выполнение метода
Основы
Продолжим работать с предыдущим примером.
JVM — это абстрактная стековая машина, которая выполняет инструкции последовательно одно за другим. Перед каждым методом указывается размер используемого стека (стек здесь — стандартная структура вида LIFO — первый вошел, последний вышел) и количество используемых локальных переменных.
Инструкции JVM занимают 1 байт, затем следуют параметры инструкции (если они есть).
Часть инструкций привязаны к типу, с которым они работают, так например iconst_1 — работает с integer, lconst_1 — с long. Соответствие префикса от типа:
-
a — reference
-
b — byte
-
c — character
-
d — double
-
i — integer
-
f — float
-
l — long
-
s — short
Также указывается количество ячеек для локальных переменных. (размер стека и количество локальных переменных рассчитываются компилятором). В нашем случае — 3. Первые ячейки используются для передачи параметров. Так в примере нулевая ячейка занята args
(в которой записана ссылка на heap). При работе с методом класса нулевую ячейку обычно занимает ссылка на свой объект (this)
![](https://habrastorage.org/getpro/habr/upload_files/082/885/a9c/082885a9ca51f1bc14ceba3ac1acf0d7.jpg)
Порядок работы:
-
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, снимает требуемое количество значений из вершины стека для использования как параметры, снимает ссылку на объект и выполняет метод. Если метод что-то возвращает, кладет обратно на вершину стека.
Как это выглядит в динамике (анимация):
![](https://habrastorage.org/getpro/habr/upload_files/c30/d7d/e05/c30d7de05b1b1b0cd6beaaca9822526c.gif)
Реализация if
Рассмотрим такой код:
public class Main { public static void main(String[] args) { if (args.length > 2) { System.out.println("Hello"); } } }
Байткод:
0: aload_0 1: arraylength 2: iconst_2 3: if_icmple 14 6: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 9: ldc #3 // String Hello 11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: return
Принятие решения выполняется в 3 строчке — if_icmple 14
. Это старый добрый условный goto и означает — возьми два значения с вершины стека и, если первое значение меньше или равно второму, перейди на 14 строчку, если нет, то продолжи выполнение дальше.
![](https://habrastorage.org/getpro/habr/upload_files/1d1/a4e/e3b/1d1a4ee3bafe76dec8e549f6a2fe434e.jpg)
Реализация циклов
Циклы стандартно реализуются аналогично if.
Рассмотрим исходный код и его байткод:
public class Main { public static void main(String[] args) { for (int i = 0; i < 2; i++) { System.out.println("Hello"); } } }
0: iconst_0 1: istore_1 2: iload_1//храним счетчик 3: iconst_2//загружаем с чем сравниваем 4: if_icmpge 21//если условие не выполняется, то выходим из цикла 7: getstatic #2 // Кладем на стек объект PrintStream 10: ldc #3 // Кладем на стек String Hello 12: invokevirtual #4 // вызываем Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: iinc 1, 1 //увеличиваем счетчик на 1 18: goto 2 //безусловный переход на 2 21: return
Реализация switch
В некоторых случаях вместо набора сравнений используется более быстрый механизм — lookupswitch или tableswitch (lookupswitch использует таблицу переходов с ключами и метками куда переходить, tableswitch — используется таблицу только с метками).
public class Main { public static void main(String[] args) { switch (args.length) { case 0: System.out.println("Hello"); break; case 1: System.out.println("World"); break; default: System.out.println("Hello, World!"); } } }
Bytecode
0: aload_0 1: arraylength 2: lookupswitch { // 2 0: 28 1: 39 default: 50 } 28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 31: ldc #3 // String Hello 33: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 36: goto 58 39: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 42: ldc #5 // String World 44: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 47: goto 58 50: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 53: ldc #6 // String Hello, World! 55: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 58: return
![](https://habrastorage.org/getpro/habr/upload_files/042/dba/dff/042dbadff55bdc0ba5def0371316fed2.jpg)
Реализация throw
public class Main { public static void main(String[] args) { try { try { throw new RuntimeException("1"); } catch (RuntimeException e) { System.out.println("Hello"); throw e; } catch (Exception e) { System.out.println("World"); throw e; } } catch (Exception e) { System.out.println("Hello, World"); } } }
При выполнении он выведет:
Hello Hello, World
Bytecode:
0: new #2 // class java/lang/RuntimeException 3: dup 4: ldc #3 // String 1 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V 9: athrow 10: astore_1 11: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 14: ldc #6 // String Hello 16: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 19: aload_1 20: athrow 21: astore_1 22: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 25: ldc #9 // String World 27: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: aload_1 31: athrow 32: astore_1 33: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 36: ldc #10 // String Hello, World 38: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: return Exception table: from to target type 0 10 10 Class java/lang/RuntimeException 0 10 21 Class java/lang/Exception 0 32 32 Class java/lang/Exception
строчки 1-4 — создание объекта исключения,
athrow — выброс этого исключения.
Здесь добавилась Exception table, в которой строки отсортированы таким образом, что виртуальная машина проверяет соответствие строки и вида исключения сверху вниз с последней точки, где она остановилась.
Когда мы дошли до 9 строчки, виртуальная машина выбросила исключение. И начала смотреть в Exception table. Первая запись удовлетворяет условиям использования (строчка 9 находится в интервале от 0 до 10 и указан RuntimeException) и поэтому выполняется переход на строчку 10.
![](https://habrastorage.org/getpro/habr/upload_files/333/c3b/66f/333c3b66ff3001f775bb50978f17c1c3.jpg)
Дальше мы доходим до строчки 31 и снова выполняется выброс исключения. Смотреть мы теперь начинаем с 2 строчки ExceptionTable. Она нам не подходит и поэтому переходим на 3, которая подходит, и выполняем переход на 32 строчку.
![](https://habrastorage.org/getpro/habr/upload_files/664/1b7/8ac/6641b78ace251d31fa4ddf0813a0de94.jpg)
Вызовы методов (opcode: invokestatic, invokespecial, invokevirtual, invokeinterface, invokedynamic)
invokestatic
Используется для вызова статического метода. (Используется статическое связывание /Static Dispatch)
public class Main { public static void main(String[] args) { int a = getNumber(1); } private static int getNumber(int i) { return i; } }
stack=1, locals=2, args_size=1 0: iconst_1 1: invokestatic #2 // Method getNumber:(I)I 4: istore_1 5: return
invokespecial
Используется для прямого вызова методов объекта текущего класса, конструкторов и методов родительского класса (Используется статическое связывание/Static Dispatch).
При этом первым параметром передается ссылка на объект.
public class Main { public static class Son extends Parent{ public Son() { new Object(); int number = super.getNumber(); int number2 = getNumber2(); } private int getNumber2() { return 0; } public int getNumber(){ return 2; } } public static class Parent extends GranParent{ public int getNumber(){ return 1; } } public static class GranParent{ public int getNumber(){ return 1; } } }
javac Main.java
javap -p -v Main$Son
Bytecode для конструктора класса Son
0: aload_0 1: invokespecial #1// Method Main$Parent."<init>":()V 4: new #2 // class java/lang/Object 7: dup 8: invokespecial #3// Method java/lang/Object."<init>":()V 11: pop 12: aload_0 13: invokespecial #4// Method Main$Parent.getNumber:()I 16: istore_1 17: aload_0 18: invokespecial #5 // Method getNumber2:()I 21: istore_2 22: return
![](https://habrastorage.org/getpro/habr/upload_files/1c0/24a/16d/1c024a16d12f6a0ee3f782078593ab59.jpg)
invokevirtual
Используется для вызова методов класса, при этом используется динамический поиск какой метод вызывать, основываясь на классе (Dynamic Dispatch). Так как методы могут быть переопределены, то сначала проверяется наличие метода в переданном классе, потом в родительском и так далее. Для поиска в HotSpot используется специальная таблица методов. Подробности можно посмотреть тут. При этом первым параметром передается ссылка на объект.
Немного изменим предыдущий пример
public class Main { public static class Son extends Parent{ public Son() { int number = getNumber(); int number2 = getNumber2(); } public int getNumber2() { return 0; } int getNumber(){ return 2; } } public static class Parent extends GranParent{ int getNumber(){ return 1; } } public static class GranParent{ int getNumber(){ return 1; } } }
Bytecode для конструктора класса Son
0: aload_0 1: invokespecial #1 // Method Main$Parent."<init>":()V 4: aload_0 5: invokevirtual #2 // Method getNumber:()I 8: istore_1 9: aload_0 10: invokevirtual #3 // Method getNumber2:()I 13: istore_2 14: return
Теперь в строчках 5 и 10 используется invokevirtual.
invokeinterface
Используется для вызова методов интерфейса (Dynamic Dispatch). При этом первым параметром передается ссылка на объект.
public class Main { public static void main(String[] args) { ParentInterface son = new Son(); son.getNumber(); } public static class Son implements ParentInterface { public int getNumber() { return 2; } } public interface ParentInterface { int getNumber(); } }
Метод main
0: new #2 // class Main$Son 3: dup 4: invokespecial #3 // Method Main$Son."<init>":()V 7: astore_1 8: aload_1 9: invokeinterface #4, 1 // InterfaceMethod Main$ParentInterface.getNumber:()I 14: pop 15: return
На 9 строчке вызывается метод интерфейса
invokedynamic
Вызов динамически-вычисляемых call sites. Сейчас, например, используется в Java для создания объектов для лямбд. Описание, как работает данный opcode, займет не одну статью и лучше всего посмотреть тут
Справочник основных opcode
Полная и единственно достоверная информация находится на сайте Oracle.
Здесь и далее сначала приводится мнемоническое название (mnemonic), потом код действия (opcode), дальше приводится состояние стека до выполнения команды, на следующей строчке — после выполнения команды, дальше, если требуется, формат команды, краткое описание на русском и пример кода, где встречается эта команда
aaload (0x32) , baload (0x33), caload (0x34), daload (0x31), iaload (0x2e), faload (0x30), laload (0x2f), saload (0x35)
…, arrayref, index →
…, value
Загрузка ссылки из массива (a* — ссылок, b* — byte или boolean, c* — char, d* — double, i* — integer, f* — float, l* — long, s* — short)
Пример
public class App { public static void main(String[] args) { String arg = args[0]; } }
0: aload_0 1: iconst_0 2: aaload 3: astore_1 4: return
aastore (0x53) , bastore (0x54), castore (0x55), dastore (0x52), iastore (0x4а), fastore (0x51), lastore (0x50), sastore (0x56)
…, arrayref, index, value →
…
Записать элемент в массив (a* — ссылок, b* — byte или boolean, c* — char, d* — double, i* — integer, f* — float, l* — long, s* — short)
Пример
public class App { public static void main(String[] args) { args[0] = "Hello"; } }
0: aload_0 1: iconst_0 2: ldc #2 // String Hello 4: aastore 5: return
aconst_null (0x1)
… →
…, null
Положить на стек null
Пример
public class App { public static void main(String[] args) { String s = null; } }
0: aconst_null 1: astore_1 2: return
aload (0x30) , dload (0x18), iload (0x15), fload (0x17), lload (0x16)
Для aload
… →
…, objectref
Для остальных
… →
…, value
Формат: dload index
Загрузить значение из локальной переменной под индекcом index (a* — ссылок, d* — double, i* — integer, f* — float, l* — long).
aload_{x} , dload_{x}, iload_{x}, fload_{x} , lload_{x}
opcode
aload_0 = (0x2a)
aload_1 = (0x2b)
aload_2 = (0x2c)
aload_3 = (0x2d)
dload_0 = (0x26)
dload_1 = (0x27)
dload_2 = (0x28)
dload_3 = (0x29)
iload_0 = (0x1a)
iload_1 = (0x1b)
iload_2 = (0x1c)
iload_3 = (0x1d)
fload_0 = (0x22)
fload_1 = (0x23)
fload_2 = (0x24)
fload_3 = (0x25)
lload_0 = (0x1e)
lload_1 = (0x1f)
lload_2 = (0x20)
lload_3 = (0x21)
x — от 0 до 3 включительно
Для aload_{x}
… →
…, objectref
Для остальных
… →
…, value
Формат: dload_1
Загрузить значение из локальной переменной под индекcом x (a* — ссылок, d* — double, i* — integer, f* — float, l* — long).
Пример
public class App { public static void main(String[] args) { int a = 35; } }
0: bipush 35 2: istore_1 3: return
anewarray (0xbd )
…, count →
…, arrayref
Формат:
anewarray
indexbyte1
indexbyte2
Создает массив объектов
Пример
public class Main { public static void main(String[] args) { Object[] objects = new Object[]{}; } }
0: iconst_0 1: anewarray #2 // class java/lang/Object 4: astore_1 5: return
areturn (0xb0), dreturn (0xaf), ireturn (0xac), freturn (0xae) , lreturn (0xad)
Для areturn
…, objectref →
[empty]
Для остальных
…, value →
[empty]
Возвращает значение со стека из метода (a* — ссылок, d* — double, i* — integer, f* — float, l* — long).
Пример
public static int get() { return 2; }
0: iconst_2 1: ireturn
arraylength (0xbe)
…, arrayref →
…, length
Получить длину массива
Пример
public class Main { public static void main(String[] args) { int length = args.length; } }
0: aload_0 1: arraylength 2: istore_1 3: return
astore (0x3a) , dstore (0x39), istore (0x36), fstore (0x38), lstore (0x37)
Для astore
…, objectref →
…
Для остальных
…, value →
…
Формат: astore index
Записать значение с вершины стека в локальную переменную под индекcом index (a* — для ссылок, d* — double, i* — integer, f* — float, l* — long).
astore_{x} , dstore_{x}, istore_{x}, fstore_{x} , lstore_{x}
opcode
astore_0 = (0x4b)
astore_1 = (0x4c)
astore_2 = (0x4d)
astore_3 = (0x4e)
dstore_0 = (0x47)
dstore_1 = (0x48)
dstore_2 = (0x49)
dstore_3 = (0x4a)
istore_0 = (0x3b)
istore_1 = (0x3c)
istore_2 = (0x3d)
istore_3 = (0x3e)
fstore_0 = (0x43)
fstore_1 = (0x44)
fstore_2 = (0x45)
fstore_3 = (0x46)
lstore_0 = (0x3f)
lstore_1 = (0x40)
lstore_2 = (0x41)
lstore_3 = (0x42)
x — от 0 до 3 включительно
Для aload_{x}
…, objectref →
…
Для остальных
…, value →
…
Формат: lstore_0
Загрузить значение из локальной переменной под индекcом {x} (a* — ссылок, d* — double, i* — integer, f* — float, l* — long).
Пример
public static boolean check(int i) { return 1 == i; }
0: iconst_1 1: iload_0 2: if_icmpne 9 5: iconst_1 6: goto 10 9: iconst_0 10: ireturn
athrow (0xbf)
….., objectref →
objectref
Выбрасывает исключение objectref.
Пример
public class App { public static void main(String[] args) { throw new RuntimeException(); } }
0: new #2 // class java/lang/RuntimeException 3: dup 4: invokespecial #3 // Method java/lang/RuntimeException."<init>":()V 7: athrow
bipush (0x10), sipush (0x11)
… →
…, value
Формат: bipush byte, sipush byte1 byte2
Кладет значение на стек (b* — byte, s* — short)
Пример
public class App { public static void main(String[] args) { short a = 1000; } }
0: sipush 1000 3: istore_1 4: return
breakpoint (0xca)
Используется для дебага, в байткоде .class не используется.
checkcast (0xc0)
…, objectref →
…, objectref
Формат: checkcast indexbyte1 indexbyte2
Проверяет, является ли объект переданным типом. Если нет, то выбрасывает — ClassCastException.
Пример
public class App { public static void main(String[] args) { List a = new ArrayList(); a.add("String"); Integer o = (Integer) a.get(0); } }
0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String String 11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 16: pop 17: aload_1 18: iconst_0 19: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 24: checkcast #7 // class java/lang/Integer 27: astore_2 28: return
d2f (0x90), d2i (0x8e), d2l (0x8f), f2d (0x8d), f2i(0x8b), f2l (0x8c), i2b (0x91), i2c(0x92), i2d(0x87), i2f(0x86), i2l(0x85), i2s(0x93), l2d(0x8a), l2f(0x89), l2i(0x88)
…, value →
…, result
Конвертация значений (d — double, i — int, f — float, s — short, c — char, l — long)
Пример
public class App { public static void main(String[] args) { int i = 2; long a = i; } }
0: iconst_2 1: istore_1 2: iload_1 3: i2l 4: lstore_2 5: return
dadd (0x63), fadd(0x62), iadd(0x60), ladd(0x61), ddiv(0x6f), dmul(0x6b), drem (0x73), dsub (0x67), idiv(0x6c), imul (0x68), irem (0x70), isub (0x64), fdiv(0x6e), fmul (0x6a), frem (0x72), fsub (0x66), ldiv(0x6d), lmul (0x69), lrem (0x71), lsub (0x65)
…, value1, value2 →
…, result
*add — cкладывает два значения
*div — делит два значения
*mul — умножает два значения
*rem — остаток от деления
*sub — вычитание
(d — double, i — int, f — float, l — long)
Пример
public class App { public static void main(String[] args) { int i = 2; int a = i + 3; } }
0: iconst_2 1: istore_1 2: iload_1 3: iconst_3 4: iadd 5: istore_2 6: return
dcmpg (0x98), dcmpl(0x97), fcmpg(0x96), fcmpl(0x95), lcmp(0x94)
…, value1, value2 →
…, result
Сравнивают 2 значения. Если первое больше, то на стек кладется 1, если равны — 0, если первое меньше, то кладется -1. (*cmpg и *cmpl различаются в работе с NaN, первое кладет 1, второе — «-1»), (d — double, f — float, l — long)
Пример
public class App { public static void main(String[] args) { double d = 9; boolean a = d > 2; } }
0: ldc2_w #2 // double 9.0d 3: dstore_1 4: dload_1 5: ldc2_w #4 // double 2.0d 8: dcmpl 9: ifle 16 12: iconst_1 13: goto 17 16: iconst_0 17: istore_3 18: return
dconst_0 (0xe), dconst_1(0xf), lconst_0(0x9), lconst_1(0xa), fconst_0(0xb), fconst_1(0xc), fconst_2(0xd), iconst_m1 (0x3), iconst_1(0x4), iconst_2 (0x5), iconst_3 (0x6), iconst_4 (0x7), iconst_5 (0x8)
… →
…, <d>
Кладет значение на стек (*_0 -> 0, *_1 -> 1, *_2 -> 2, *_3 -> 3, *_4 -> 4, *_5 -> 5, *_m1 -> -1), (d* — double, i* — int, f* — float, l* — long)
Пример
public class App { public static void main(String[] args) { double d = 1; } }
0: dconst_1 1: dstore_1 2: return
dneg (0x77), ineg (0x74), fneg(0x76), lneg(0x75)
…., value →
…, result
возвращает обратное значение
(d — double, i — int, f — float, l — long)
Пример
public class Main { public static void main(String[] args) { int i = 2; int b = -i; } }
0: iconst_2 1: istore_1 2: iload_1 3: ineg 4: istore_2 5: return
dup(0x59)
…, value →
…, value, value
Дублирует верх стека
Пример
public class Main { public static void main(String[] args) { Object a = new Object(); } }
0: new #2 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: astore_1 8: return
dup_x1(0x5a)
…, value2, value1 →
…, value1(новое), value2, value1
Дублирует верх стека и вставляет его на 1 или 2 значения ниже
dup_x2(0x5b)
…, value3, value2, value1 →
…, value1 (новое), value3, value2, value1
Дублирует верх стека и вставляет его на 2 или 3 значения ниже
dup2 (0x5c)
…, value2, value1 →
…, value2, value1, value2(новое), value1(новое)
Дублирует 1 или 2 значения с верха стека
dup2_x1(0x5d)
…, value3, value2, value1 →
…, value2(новое), value1(новое), value3, value2, value1
Дублирует 1 или 2 значения с вершины стека и вставляет его на 2 или 3 значения ниже
dup2_x2 (0x5e)
…, value4, value3, value2, value1 →
…, value2(новое), value1(новое), value4, value3, value2, value1
Дублирует 1 или 2 значения с вершины стека и вставляет его на 2, 3 или 4 значения ниже
getfield (0xb4)
…, objectref →
…, value
Формат: getfield indexbyte1 indexbyte2
Положить на стек значение из поля
Пример
public class App { public static void main(String[] args) { int j = new MyClass().i; } public static class MyClass{ public int i = 1; } }
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class org/example/App$MyClass 3: dup 4: invokespecial #3 // Method org/example/App$MyClass."<init>":()V 7: getfield #4 // Field org/example/App$MyClass.i:I 10: istore_1 11: return
getstatic (0xb2)
…, →
…, value
Формат: getstatic indexbyte1 indexbyte2
Положить на стек значение из статического поля
Пример
public class App { static int i = 0; public static void main(String[] args) { int j = i; } }
0: getstatic #2 // Field i:I 3: istore_1 4: return
goto (0xa7), goto_w (0xc8)
Формат:
goto indexbyte1 indexbyte2
goto_w indexbyte1 indexbyte2 indexbyte3 indexbyte4
Прыгнуть
Пример
public class App { public static void main(String[] args) { int i; if (args.length == 1) { i = 1; } else { i=2; } } }
0: aload_0 1: arraylength 2: iconst_1 3: if_icmpne 11 6: iconst_1 7: istore_1 8: goto 13 11: iconst_2 12: istore_1 13: return
iand (0x7e), land (0x7f), ior (0x80), lor(0x81), ixor(0x82), lxor(0x83)
…, value1, value2 →
…, result
Выполнить для *and — &, для *or — |, для *xor — логическое XOR (l* — для long, i* — для int)
Пример
public class App { public static void main(String[] args) { int i = 1; int j = 1 & i; } }
0: iconst_1 1: istore_1 2: iconst_1 3: iload_1 4: iand 5: istore_2 6: return
if_acmpeq (0xa5), if_acmpne(0xa6)
…, value1, value2 →
…
Формат: if_acmp<cond> branchbyte1 branchbyte2
Прыгнуть, если ссылки ссылаются на один и тот же объект (для if_acmpeq), не ссылаются на один и тот же объект (для if_acmpne)
Пример
public class App { public static void main(String[] args) { int i = 0; if ("s" == args[0]) { i = 1; } else { i = 2; } } }
0: iconst_0 1: istore_1 2: ldc #2 // String s 4: aload_0 5: iconst_0 6: aaload 7: if_acmpne 15 10: iconst_1 11: istore_1 12: goto 17 15: iconst_2 16: istore_1 17: return
if_icmpeq (0x9f), if_icmpne(0xa0), if_icmplt(0xa1), if_icmpge(0xa2), if_icmpgt(0xa3), if_icmple(0xa4)
…, value1, value2 →
…
Формат: if_icmp<cond> branchbyte1 branchbyte2
Прыгнуть, если (value1 и value2 — int):
-
if_icmpeq — если value1 == value2
-
if_icmpne — если value1 ≠ value2
-
if_icmplt — если value1 < value2
-
if_icmple — если value1 ≤ value2
-
if_icmpgt — если value1 > value2
-
if_icmpge — если value1 ≥ value2
Пример
public class App { public static void main(String[] args) { int i = 0; if (i==args.length) { i = 1; } else { i = 2; } } }
0: iconst_0 1: istore_1 2: iload_1 3: aload_0 4: arraylength 5: if_icmpne 13 8: iconst_1 9: istore_1 10: goto 15 13: iconst_2 14: istore_1 15: return
ifeq (0x99), ifne(0x9a), iflt(0x9b), ifge(0x9c), ifgt(0x9d), ifle(0x9e)
…, value →
…
Формат: if<cond> branchbyte1 branchbyte2
Прыгнуть, если (value- int):
-
ifeq — если value == 0
-
ifne — если value ≠ 0
-
iflt — если value < 0
-
ifle — если value ≤ 0
-
ifgt — если value > 0
-
ifge — если value ≥ 0
Пример
public class App { public static void main(String[] args) { long i = 0; if (i==args.length) { i = 1; } else { i = 2; } } }
0: lconst_0 1: lstore_1 2: lload_1 3: aload_0 4: arraylength 5: i2l 6: lcmp 7: ifne 15 10: lconst_1 11: lstore_1 12: goto 19 15: ldc2_w #2 // long 2l 18: lstore_1 19: return
ifnonnull (0xc7), ifnull(0xc6)
…, value →
…
Формат: if<cond> branchbyte1 branchbyte2
Прыгнуть, если (value- объект):
-
ifnull — если value == null
-
ifnonnull — если value ≠ null
Пример
public class App { public static void main(String[] args) { if (new Object() != null) { int a = 1; } } }
0: new #2 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: ifnull 12 10: iconst_1 11: istore_1 12: return
iinc (0x84)
Формат: iinc index const
Увеличить локальную переменную (int) на константу
Пример
public class App { public static void main(String[] args) { int i = args.length; i += 10; } }
0: aload_0 1: arraylength 2: istore_1 3: iinc 1, 10 6: return
impdep1 (0xfe), impdep2(0xff)
Зарезервированные opcode
instanceof (0xc1)
…, objectref →
…, result
Формат: instanceof indexbyte1 indexbyte2
Кладет на стек 1, если объект данного типа, если нет — то кладет 0
Пример
public class App { public static void main(String[] args) { if(args instanceof Object){ int i = 0; } } }
0: aload_0 1: instanceof #2 // class java/lang/Object 4: ifeq 9 7: iconst_0 8: istore_1 9: return
invokedynamic (0xba)
…, [arg1, [arg2 …]] →
…
Формат: invokedynamic indexbyte1 indexbyte2 0 0
Краткое объяснение приведено выше в статье
invokeinterface (0xb9)
…, objectref, [arg1, [arg2 …]] →
…
Формат: invokeinterface indexbyte1 indexbyte2 0 0
Краткое объяснение приведено выше в статье
invokespecial (0xb7)
…, objectref, [arg1, [arg2 …]] →
…
Формат: invokespecial indexbyte1 indexbyte2
Краткое объяснение приведено выше в статье
invokestatic (0xb8)
…, [arg1, [arg2 …]] →
…
Формат: invokestatic indexbyte1 indexbyte2
Краткое объяснение приведено выше в статье
invokevirtual (0xb6)
…, objectref, [arg1, [arg2 …]] →
…
Формат: invokevirtual indexbyte1 indexbyte2
Краткое объяснение приведено выше в статье
iushr (0x7c), lushr(0x7d)
…, value1, value2 →
…, result
Логический сдвиг вправо. (i* — int, l* — long)
Пример
public class App { public static void main(String[] args) { int i = args.length >>> 1; } }
0: aload_0 1: arraylength 2: iconst_1 3: iushr 4: istore_1 5: return
ishl (0x78), lshl(0x79), ishr(0x7a), lshr(0x7b)
…, value1, value2 →
…, result
Сдвиг влево — для *shl , вправо — для *shr. (i* — int, l* — long)
Пример
public class App { public static void main(String[] args) { int i = args.length << 1; } }
0: aload_0 1: arraylength 2: iconst_1 3: ishl 4: istore_1 5: return
jsr (0xa8), jsr_w(0xc9)
Для class файла версии 51 или старше не используются. Использовались для реализации try-finally
ldc(0x12), ldc_w(0x13), ldc2_w(0x14)
… →
…, value
Формат:
ldc indexbyte1
ldc_w indexbyte1 indexbyte2
ldc2_w indexbyte1 indexbyte2
Кладет константу на стек. ldc_w — использует широкий индекс, ldc2_w используется для long и double
Пример
public class Main { public static void main(String[] args) { long a = 10l; } }
0: ldc2_w #2 // long 10l 3: lstore_1 4: return
lookupswitch(0xab)
…, key →
…
Формат:
lookupswitch <0-3 byte pad> defaultbyte1 defaultbyte2 defaultbyte3 defaultbyte4 npairs1 npairs2 npairs3 npairs4 match-offset pairs…
Используется для перехода к строчке в соответствии с таблицей соответствия. См. реализацию switch
monitorenter(0xc2), monitorexit(0xc3)
…, objectref →
…
Взятие и отпуск монитора
Пример
public class Main { static Object o = new Object(); public static void main(String[] args) { synchronized (o) { int a = 1; } } }
0: getstatic #2 // Field o:Ljava/lang/Object; 3: dup 4: astore_1 5: monitorenter 6: iconst_1 7: istore_2 8: aload_1 9: monitorexit 10: goto 18 13: astore_3 14: aload_1 15: monitorexit 16: aload_3 17: athrow 18: return
multianewarray(0xc5)
… →…, count1, [count2, …] →
…, arrayref
Формат:
multianewarray indexbyte1 indexbyte2 dimensions
Создает многоразмерный массив
Пример
public class Main { public static void main(String[] args) { int[][] array = new int[2][2]; } }
0: iconst_2 1: iconst_2 2: multianewarray #2, 2 // class "[[I" 6: astore_1 7: return
new (0xbb)
… →
…, objectref
Формат:
new indexbyte1 indexbyte2
Создает новый объект, не вызывая его конструктор
Пример
public class Main { public static void main(String[] args) { Object o = new Object(); } }
0: new #2 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: astore_1 8: return
newarray (0xbc)
…, count →
…, arrayref
Формат:
newarray atype
Создает новый массив
Пример
public class Main { public static void main(String[] args) { int[] i = new int[2]; } }
0: iconst_2 1: newarray int 3: astore_1 4: return
nop (0x0)
Не делает ничего
pop (0x57)
…., value →
…
Выбрасывает 1 элемент с вершины стека
pop2(0x58)
…, value2, value1 →
…
Выбрасывает 1 или 2 элемента с вершины стека
putfield (0xb5 )
…, objectref, value →
…
Формат: putfield indexbyte1 indexbyte2
Вставить в поле объекта значение с верха стека
Пример
public class Main { public static void main(String[] args) { new MyClass().i = 2; } public static class MyClass { public int i = 1; } }
0: new #2 // class Main$MyClass 3: dup 4: invokespecial #3 // Method Main$MyClass."<init>":()V 7: iconst_2 8: putfield #4 // Field Main$MyClass.i:I 11: return
putstatic (0xb3 )
…, value →
…
Формат: putstatic indexbyte1 indexbyte2
Вставить в статическое поле значение с верха стека
Пример
public class Main { static int i = 0; public static void main(String[] args) { i=2; } }
0: iconst_0 1: putstatic #2 // Field i:I 4: return
ret (0xa9)
Статус opcode не ясен, так как использовался jsr
return(0xb1)
… →
[empty]
Возвращает void из метода
swap (0xb1)
…, value2, value1 →
…, value1, value2
Меняет местами два верхних элемента стека
tableswitch (0xaa)
…, index →
…
Формат:
tableswitch
<0-3 byte pad>
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
lowbyte1
lowbyte2
lowbyte3
lowbyte4
highbyte1
highbyte2
highbyte3
highbyte4
jump offsets…
Одна из реализаций switch
wide (0xc4)
Расширяет индекс локальной переменной дополнительными байтами. Пример
(no name) (0xcb-fd )
Не используются на данный момент
Ссылки
ссылка на оригинал статьи https://habr.com/ru/articles/568402/
Добавить комментарий