Как стать автором
Обновить

Основы Java Bytecode

Время на прочтение 29 мин
Количество просмотров 28K

Что будет и кому может быть интересно

Внимание, статья содержит довольно много картинок и получилась довольно тяжелой и объемной

Как и многие базовые вещи, на habr уже были статьи о bytecode (раз, два), основные же отличия данной статьи - в попытке визуализировать, что происходит внутри, и краткий справочник инструкций (может кому пригодится), многие с примерами использования.

В данной статье будут рассмотрены только основы Java Bytecode. Если вы уже знакомы с его основами, статья вряд ли будет вам интересна, так как практически все можно найти в документации.

В данной статье не рассмотрены многие темы (например, фреймы, многие атрибуты), иначе она бы получилась еще больше

Зачем знать что-то о Bytecode

Тема bytecode довольно скучная и в реальной работе среднестатистического программиста практически не используется.

Так почему стоит знать про основы bytecode:

  • Потому что с этим работает 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
Просмотр для Kotlin
Просмотр для Kotlin

Что содержит .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];
}

u1u2, 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 (Интерпретация)

ACC_PUBLIC

0x0001

public, виден за пределами пакета

ACC_FINAL

0x0010

final; не может быть наследован

ACC_SUPER

0x0020

Обработка методов супер-класса когда вызывается инструкция invokespecial. (ссылка на более подробное объяснение)

ACC_INTERFACE

0x0200

Интерфейс, не класс

ACC_ABSTRACT

0x0400

Абстрактный, не может быть наследован

ACC_SYNTHETIC

0x1000

Синтетический, не из кода

ACC_ANNOTATION

0x2000

Аннотация

ACC_ENUM

0x4000

Enum

ACC_MODULE

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)

Порядок работы:

  • 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, снимает требуемое количество значений из вершины стека для использования как параметры, снимает ссылку на объект и выполняет метод. Если метод что-то возвращает, кладет обратно на вершину стека.

Как это выглядит в динамике (анимация):

Реализация 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 строчку, если нет, то продолжи выполнение дальше.

Реализация циклов

Циклы стандартно реализуются аналогично 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

Реализация 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.

Дальше мы доходим до строчки 31 и снова выполняется выброс исключения. Смотреть мы теперь начинаем с 2 строчки ExceptionTable. Она нам не подходит и поэтому переходим на 3, которая подходит, и выполняем переход на 32 строчку.

Вызовы методов (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

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)

..., arrayrefindex →

..., 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)

..., arrayrefindexvalue →

...

Записать элемент в массив (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)

..., value1value2 →

..., 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)

..., value1value2 →

..., 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 →

..., valuevalue

Дублирует верх стека

Пример
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)

..., value2value1 →

..., value1(новое)value2value1

Дублирует верх стека и вставляет его на 1 или 2 значения ниже

dup_x2(0x5b)

..., value3value2value1 →

..., value1 (новое)value3value2value1

Дублирует верх стека и вставляет его на 2 или 3 значения ниже

dup2 (0x5c)

..., value2value1 →

..., value2value1value2(новое)value1(новое)

Дублирует 1 или 2 значения с верха стека

dup2_x1(0x5d)

..., value3value2value1 →

..., value2(новое)value1(новое)value3value2value1

Дублирует 1 или 2 значения с вершины стека и вставляет его на 2 или 3 значения ниже

dup2_x2 (0x5e)

..., value4value3value2value1 →

..., value2(новое)value1(новое)value4value3value2value1

Дублирует 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)

..., value1value2 →

..., 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)

..., value1value2 →

...

Формат: 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)

..., value1value2 →

...

Формат: 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)

..., value1value2 →

..., 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)

..., value1value2 →

..., 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)

..., value2value1 →

...

Выбрасывает 1 или 2 элемента с вершины стека

putfield (0xb5 )

..., objectrefvalue →

...

Формат: 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)

..., value2value1 →

..., value1value2

Меняет местами два верхних элемента стека

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 )

Не используются на данный момент

Ссылки

Теги:
Хабы:
+14
Комментарии 5
Комментарии Комментарии 5

Публикации

Истории

Работа

Java разработчик
359 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн