字节码指令

作者: 我可能是个假开发 | 来源:发表于2024-01-05 18:41 被阅读0次

    一、javap 工具

    Oracle 提供了 javap 工具来反编译 class 文件

    // 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
    package jvm.bytecode;
    
    public class HelloWorld {
        public static void main(String[] args) {
            System.out.println("hello,world");
        }
    }
    
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v HelloWorld.class    
    Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/HelloWorld.class
      Last modified 2023-12-22; size 589 bytes
      MD5 checksum 4a8677ad7fb4ea31018c87f6c29b4592
      Compiled from "HelloWorld.java"
    public class jvm.bytecode.HelloWorld
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#21         // java/lang/Object."<init>":()V
       #2 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;
       #3 = String             #24            // hello,world
       #4 = Methodref          #25.#26        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #5 = Class              #27            // jvm/bytecode/HelloWorld
       #6 = Class              #28            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               LocalVariableTable
      #12 = Utf8               this
      #13 = Utf8               Ljvm/bytecode/HelloWorld;
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               args
      #17 = Utf8               [Ljava/lang/String;
      #18 = Utf8               MethodParameters
      #19 = Utf8               SourceFile
      #20 = Utf8               HelloWorld.java
      #21 = NameAndType        #7:#8          // "<init>":()V
      #22 = Class              #29            // java/lang/System
      #23 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;
      #24 = Utf8               hello,world
      #25 = Class              #32            // java/io/PrintStream
      #26 = NameAndType        #33:#34        // println:(Ljava/lang/String;)V
      #27 = Utf8               jvm/bytecode/HelloWorld
      #28 = Utf8               java/lang/Object
      #29 = Utf8               java/lang/System
      #30 = Utf8               out
      #31 = Utf8               Ljava/io/PrintStream;
      #32 = Utf8               java/io/PrintStream
      #33 = Utf8               println
      #34 = Utf8               (Ljava/lang/String;)V
    {
      public jvm.bytecode.HelloWorld();
        descriptor: ()V
        flags: 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 11: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Ljvm/bytecode/HelloWorld;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: 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 14: 0
            line 15: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  args   [Ljava/lang/String;
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "HelloWorld.java"
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % 
    

    二、方法执行流程

    package jvm.bytecode;
    
    /**
     * 字节码指令 和 操作数栈、常量池的关系
     */
    public class MethodInvokeDemo {
        public static void main(String[] args) {
            int a = 10;
            int b = Short.MAX_VALUE + 1;
            int c = a + b;
            System.out.println(c); //32778
        }
    }
    
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v MethodInvokeDemo.class
    Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/MethodInvokeDemo.class
      Last modified 2023-12-22; size 666 bytes
      MD5 checksum d75beb1197a2a208adbc21ac216fdaf9
      Compiled from "MethodInvokeDemo.java"
    public class jvm.bytecode.MethodInvokeDemo
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #7.#26         // java/lang/Object."<init>":()V
       #2 = Class              #27            // java/lang/Short
       #3 = Integer            32768
       #4 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V
       #6 = Class              #32            // jvm/bytecode/MethodInvokeDemo
       #7 = Class              #33            // java/lang/Object
       #8 = Utf8               <init>
       #9 = Utf8               ()V
      #10 = Utf8               Code
      #11 = Utf8               LineNumberTable
      #12 = Utf8               LocalVariableTable
      #13 = Utf8               this
      #14 = Utf8               Ljvm/bytecode/MethodInvokeDemo;
      #15 = Utf8               main
      #16 = Utf8               ([Ljava/lang/String;)V
      #17 = Utf8               args
      #18 = Utf8               [Ljava/lang/String;
      #19 = Utf8               a
      #20 = Utf8               I
      #21 = Utf8               b
      #22 = Utf8               c
      #23 = Utf8               MethodParameters
      #24 = Utf8               SourceFile
      #25 = Utf8               MethodInvokeDemo.java
      #26 = NameAndType        #8:#9          // "<init>":()V
      #27 = Utf8               java/lang/Short
      #28 = Class              #34            // java/lang/System
      #29 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
      #30 = Class              #37            // java/io/PrintStream
      #31 = NameAndType        #38:#39        // println:(I)V
      #32 = Utf8               jvm/bytecode/MethodInvokeDemo
      #33 = Utf8               java/lang/Object
      #34 = Utf8               java/lang/System
      #35 = Utf8               out
      #36 = Utf8               Ljava/io/PrintStream;
      #37 = Utf8               java/io/PrintStream
      #38 = Utf8               println
      #39 = Utf8               (I)V
    {
      public jvm.bytecode.MethodInvokeDemo();
        descriptor: ()V
        flags: 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 6: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Ljvm/bytecode/MethodInvokeDemo;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=4, args_size=1
             0: bipush        10
             2: istore_1
             3: ldc           #3                  // int 32768
             5: istore_2
             6: iload_1
             7: iload_2
             8: iadd
             9: istore_3
            10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            13: iload_3
            14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
            17: return
          LineNumberTable:
            line 8: 0
            line 9: 3
            line 10: 6
            line 11: 10
            line 12: 17
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      18     0  args   [Ljava/lang/String;
                3      15     1     a   I
                6      12     2     b   I
               10       8     3     c   I
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "MethodInvokeDemo.java"
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % 
    

    1.常量池载入运行时常量池

    image.png

    整数范围内的数据跟字节码指令一起存储在方法区,超过了范围存储在常量池。

    常量池属于方法区的一个部分

    2.方法字节码载入方法区

    image.png

    3.main 线程开始运行,分配栈帧内存

    stack=2(操作数栈深度),locals=4(局部变量表长度):


    image.png

    绿色:局部变量表
    蓝色:操作数栈

    4.执行引擎开始执行字节码

    代码 a=10

    • bipush 10
    • istore_1

    bipush 10:将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有:

    • sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)
    • ldc 将一个 int 压入操作数栈
    • ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)

    这里小的数字都是和字节码指令存一起在方法区,超过 short 范围的数字存入了常量池

    image.png

    istore_1:将操作数栈顶数据弹出,存入局部变量表的 slot 1

    image.png

    代码 int b = Short.MAX_VALUE + 1;:

    • ldc #3
    • istore_2

    ldc #3:从常量池加载 #3 数据到操作数栈

    image.png

    注意 Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算好的

    istore_2:将操作数栈顶数据弹出,存入局部变量表的 slot 2

    image.png

    代码:int c = a + b;

    • iload_1
    • iload_2
    • iadd
    • istore_3

    iload_1:将slot 1的值读取到操作数栈

    image.png

    iload_2:局部变量2号槽位的值读取到操作数栈

    image.png

    iadd:弹出操作数栈的两个变量执行加法并且把结果存入操作数栈

    image.png

    istore_3:取出操作数栈的变量存入局部变量表的slot3

    image.png

    代码:System.out.println(c);

    • getstatic #4
    • iload_3
    • invokevirtual #5

    getstatic #4:到常量池中找到成员变量的引用,在堆找到System.out对象,将引用放入操作数栈

    image.png
    image.png

    iload_3:将局部变量表中slot3的c读入到操作数栈。

    image.png

    invokevirtual #5

    • 找到常量池 #5 项

    • 定位到方法区 java/io/PrintStream.println:(I)V 方法

    • 生成新的栈帧(分配 locals、stack等)

    • 传递参数,执行新栈帧中的字节码


      image.png
    • 执行完毕,弹出栈帧

    • 清除 main 操作数栈内容


      image.png

    return:完成 main 方法调用,弹出 main 栈帧,程序结束

    ++操作

    public class AddDemo {
    
        public static void main(String[] args) {
            int a = 10;
            int b = a++ + ++a + a--;
            System.out.println(a); //11
            System.out.println(b); //34
    
            /**
             *        局部变量表         操作数栈
             *        a     b
             * a++    11                 10
             * ++a    12                 12
             *  +                        10+12=22
             * a--    11                 12
             *  +           34           22+12=34
             */
        }
    }
    
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v AddDemo.class
    Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/AddDemo.class
      Last modified 2023-12-23; size 614 bytes
      MD5 checksum f73913e07ef4806789b63b28b5186492
      Compiled from "AddDemo.java"
    public class jvm.bytecode.AddDemo
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #5.#23         // java/lang/Object."<init>":()V
       #2 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
       #3 = Methodref          #26.#27        // java/io/PrintStream.println:(I)V
       #4 = Class              #28            // jvm/bytecode/AddDemo
       #5 = Class              #29            // java/lang/Object
       #6 = Utf8               <init>
       #7 = Utf8               ()V
       #8 = Utf8               Code
       #9 = Utf8               LineNumberTable
      #10 = Utf8               LocalVariableTable
      #11 = Utf8               this
      #12 = Utf8               Ljvm/bytecode/AddDemo;
      #13 = Utf8               main
      #14 = Utf8               ([Ljava/lang/String;)V
      #15 = Utf8               args
      #16 = Utf8               [Ljava/lang/String;
      #17 = Utf8               a
      #18 = Utf8               I
      #19 = Utf8               b
      #20 = Utf8               MethodParameters
      #21 = Utf8               SourceFile
      #22 = Utf8               AddDemo.java
      #23 = NameAndType        #6:#7          // "<init>":()V
      #24 = Class              #30            // java/lang/System
      #25 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
      #26 = Class              #33            // java/io/PrintStream
      #27 = NameAndType        #34:#35        // println:(I)V
      #28 = Utf8               jvm/bytecode/AddDemo
      #29 = Utf8               java/lang/Object
      #30 = Utf8               java/lang/System
      #31 = Utf8               out
      #32 = Utf8               Ljava/io/PrintStream;
      #33 = Utf8               java/io/PrintStream
      #34 = Utf8               println
      #35 = Utf8               (I)V
    {
      public jvm.bytecode.AddDemo();
        descriptor: ()V
        flags: 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 11: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Ljvm/bytecode/AddDemo;
    
      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: bipush        10
             2: istore_1
             3: iload_1
             4: iinc          1, 1
             7: iinc          1, 1
            10: iload_1
            11: iadd
            12: iload_1
            13: iinc          1, -1
            16: iadd
            17: istore_2
            18: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            21: iload_1
            22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
            25: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            28: iload_2
            29: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
            32: return
          LineNumberTable:
            line 14: 0
            line 15: 3
            line 16: 18
            line 17: 25
            line 18: 32
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      33     0  args   [Ljava/lang/String;
                3      30     1     a   I
               18      15     2     b   I
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "AddDemo.java"
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % 
    
    

    分析:

    • iinc 指令是直接在局部变量 slot 上进行运算
    • a++ 和 ++a 的区别是先执行 iload 还是 先执行 iinc

    int a = 10;:

    • bipush 10:把10放入操作数栈
    • istore_1:将10从操作数栈弹出存入局部变量表的槽位1

    bipush 10:把10放入操作数栈


    image.png

    istore_1:将10从操作数栈弹出存入局部变量表的槽位1


    image.png

    a++:

    • iload_1:将局部变量表1号槽位的10读取到操作数栈中
    • iinc 1, 1:将局部变量表1号槽位的数据自增1

    iload_1:将局部变量表1号槽位的10读取到操作数栈中


    image.png

    iinc 1, 1:将局部变量表1号槽位的数据自增1:


    image.png

    + ++a:

    • iinc 1, 1:将局部变量表1号槽位的数据自增1
    • iload_1:将局部变量表1号槽位的10读取到操作数栈中
    • iadd:将操作数栈的两个数据弹出执行加法再将结果22存入操作数栈

    iinc 1, 1:将局部变量表1号槽位的数据自增1:


    image.png

    iload_1:将局部变量表1号槽位的12读取到操作数栈中:


    image.png

    iadd:将操作数栈的两个数据弹出执行加法再将结果22存入操作数栈


    image.png

    + a--

    • iload_1:将1号槽位的12放入操作数栈
    • iinc 1, -1:将1号槽位的局部变量减1
    • iadd:将操作数栈的两个数字相加

    iload_1:将1号槽位的12放入操作数栈


    image.png

    iinc 1, -1:将1号槽位的局部变量减1


    image.png

    iadd:将操作数栈的两个数字弹出相加再存入操作数栈:


    image.png

    int b = a++ + ++a + a--; 赋值操作

    • istore_2:将操作数栈的值赋值给2号槽位的局部变量b


      image.png

    三、条件判断指令

    指令 助记符 含义
    0x99 ifeq 判断是否 == 0
    0x9a ifne 判断是否 != 0
    0x9b iflt 判断是否 < 0
    0x9c ifge 判断是否 >= 0
    0x9d ifgt 判断是否 > 0
    0x9e ifle 判断是否 <= 0
    0x9f if_icmpeq 两个int是否 ==
    0xa0 if_icmpne 两个int是否 !=
    0xa1 if_icmplt 两个int是否 <
    0xa2 if_icmpge 两个int是否 >=
    0xa3 if_icmpgt 两个int是否 >
    0xa4 if_icmple 两个int是否 <=
    0xa5 if_acmpeq 两个引用是否 ==
    0xa6 if_acmpne 两个引用是否 !=
    0xc6 ifnull 判断是否 == null
    0xc7 ifnonnull 判断是否 != null
    • byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节
    • goto 用来进行跳转到指定行号的字节码
    public class OperateDemo {
        public static void main(String[] args) {
            int a = 0;
            if(a == 0) {
                a = 10;
            } else {
                a = 20;
            }
        }
    }
    
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v OperateDemo.class
    Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/OperateDemo.class
      Last modified 2023-12-23; size 510 bytes
      MD5 checksum 9b2f54f029ae10e92aed067e468e3c17
      Compiled from "OperateDemo.java"
    public class jvm.bytecode.OperateDemo
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #3.#21         // java/lang/Object."<init>":()V
       #2 = Class              #22            // jvm/bytecode/OperateDemo
       #3 = Class              #23            // java/lang/Object
       #4 = Utf8               <init>
       #5 = Utf8               ()V
       #6 = Utf8               Code
       #7 = Utf8               LineNumberTable
       #8 = Utf8               LocalVariableTable
       #9 = Utf8               this
      #10 = Utf8               Ljvm/bytecode/OperateDemo;
      #11 = Utf8               main
      #12 = Utf8               ([Ljava/lang/String;)V
      #13 = Utf8               args
      #14 = Utf8               [Ljava/lang/String;
      #15 = Utf8               a
      #16 = Utf8               I
      #17 = Utf8               StackMapTable
      #18 = Utf8               MethodParameters
      #19 = Utf8               SourceFile
      #20 = Utf8               OperateDemo.java
      #21 = NameAndType        #4:#5          // "<init>":()V
      #22 = Utf8               jvm/bytecode/OperateDemo
      #23 = Utf8               java/lang/Object
    {
      public jvm.bytecode.OperateDemo();
        descriptor: ()V
        flags: 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 11: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Ljvm/bytecode/OperateDemo;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=2, args_size=1
             0: iconst_0
             1: istore_1
             2: iload_1
             3: ifne          12
             6: bipush        10
             8: istore_1
             9: goto          15
            12: bipush        20
            14: istore_1
            15: return
          LineNumberTable:
            line 14: 0
            line 15: 2
            line 16: 6
            line 18: 12
            line 20: 15
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      16     0  args   [Ljava/lang/String;
                2      14     1     a   I
          StackMapTable: number_of_entries = 2
            frame_type = 252 /* append */
              offset_delta = 12
              locals = [ int ]
            frame_type = 2 /* same */
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "OperateDemo.java"
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % 
    
    • 0: iconst_0 将0放入操作数栈
    • 1: istore_1 存入1号位
    • 2: iload_1:加载1号位到操作数栈
    • 3: ifne 12:操作数栈中的数是不是不等于0,满足,跳到12行
    • 6: bipush 10:将10放入操作数栈
    • 8: istore_1 :将操作数栈中的数赋值给1号槽位
    • 9: goto 15:跳转到15行
    • 12: bipush 20:将20放入操作数栈
    • 14: istore_1 :存入1号位
    • 15: return :返回

    四、循环控制指令

    1.while 循环

    public class Demo {
        public static void main(String[] args) {
            int a = 0;
            while (a < 10) {
                a++;
            }
        }
    }
    
    0: iconst_0 :声明0
    1: istore_1:赋值给1号槽位a变量
    2: iload_1: 将1号槽位a变量放入操作数栈
    3: bipush 10:10放入操作数栈
    5: if_icmpge 14:比较操作数栈的两个变量是否相同,相同则跳转到14行
    8: iinc 1, 1:1号槽位的变量自增1
    11: goto 2:跳转到第2号
    14: return
    

    2.do while 循环

    public class Demo {
        public static void main(String[] args) {
            int a = 0;
            do {
                a++;
            } while (a < 10);
        }
    }
    
    0: iconst_0
    1: istore_1
    2: iinc 1, 1
    5: iload_1
    6: bipush 10
    8: if_icmplt 2
    11: ret
    

    3.for 循环

    public class Demo {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
    
            }
        }
    }
    
    0: iconst_0
    1: istore_1
    2: iload_1
    3: bipush 10
    5: if_icmpge 14
    8: iinc 1, 1
    11: goto 2
    14: return
    

    比较 while 和 for 的字节码,它们是一模一样的

    分析

    public class CycleDemo {
        public static void main(String[] args) {
            int i = 0;
            int x = 0;
            while (i < 10) {
                x = x++;
                i++;
            }
            System.out.println(x); // 0
        }
    }
    
        Code:
          stack=2, locals=3, args_size=1
             0: iconst_0
             1: istore_1
             2: iconst_0
             3: istore_2
             4: iload_1
             5: bipush        10
             7: if_icmpge     21
            10: iload_2
            11: iinc          2, 1
            14: istore_2
            15: iinc          1, 1
            18: goto          4
            21: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
            24: iload_2
            25: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
            28: return
    

    循环里,局部变量表虽然自增了1,但是赋值操作是把操作数栈中的值赋值给局部变量表,所以最终结果还是0。

    x++操作的是局部变量的值,赋值操作是弹出栈中的值

    五、构造方法

    1.<cinit>()V

    public class Cinit {
        static {
            i = 20;
        }
        static {
            i = 30;
        }
        static int i = 10;
    
        public static void main(String[] args) {
            System.out.println(Cinit.i); // 10
        }
    }
    

    编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 <cinit>()V :

      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: bipush        20 //20放入操作数栈
             2: putstatic     #3                  // Field i:I 赋值给常量池中的i变量
             5: bipush        30 
             7: putstatic     #3                  // Field i:I
            10: bipush        10
            12: putstatic     #3                  // Field i:I
            15: return
          LineNumberTable:
            line 13: 0
            line 16: 5
            line 18: 10
    

    <cinit>()V 方法会在类加载的初始化阶段被调用

    2. <init>()V

    public class InitDemo {
    
        private String a = "s1";
    
        {
            b = 20;
        }
    
        private int b = 10;
    
        {
            a = "s2";
        }
        //放在最后
        public InitDemo(String a, int b) {
            this.a = a;
            this.b = b;
        }
    
        public static void main(String[] args) {
            InitDemo d = new InitDemo("s3", 30);
            System.out.println(d.a); //s3
            System.out.println(d.b); //30
        }
    }
    

    编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后

      public jvm.bytecode.InitDemo(java.lang.String, int);
        descriptor: (Ljava/lang/String;I)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=3
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: ldc           #2                  // String s1   s1加载到操作数栈
             7: putfield      #3                  // Field a:Ljava/lang/String;   s1赋值给this.a
            10: aload_0
            11: bipush        20
            13: putfield      #4                  // Field b:I
            16: aload_0
            17: bipush        10
            19: putfield      #4                  // Field b:I
            22: aload_0
            23: ldc           #5                  // String s2
            25: putfield      #3                  // Field a:Ljava/lang/String;
            //------------------------------------------------------------------------
            28: aload_0
            29: aload_1 //a加载到操作数栈
            30: putfield      #3                  // Field a:Ljava/lang/String; //赋值给a
            33: aload_0
            34: iload_2 //b加载到操作数栈
            35: putfield      #4                  // Field b:I //赋值给b
            //------------------------------------------------------------------------    
            38: return
          LineNumberTable:
            line 25: 0
            line 13: 4
            line 16: 10
            line 19: 16
            line 22: 22
            line 26: 28
            line 27: 33
            line 28: 38
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      39     0  this   Ljvm/bytecode/InitDemo;
                0      39     1     a   Ljava/lang/String;
                0      39     2     b   I
        MethodParameters:
    
    

    六、方法调用

    public class MethodInvoke {
    
        public MethodInvoke() { }
    
        private void test1() { }
    
        private final void test2() { }
    
        public void test3() { }
    
        public static void test4() { }
    
        @Override
        public String toString() {
            return super.toString();
        }
    
        public static void main(String[] args) {
            MethodInvoke d = new MethodInvoke();
            d.test1();
            d.test2();
            d.test3();
            d.test4();
            MethodInvoke.test4();
            d.toString();
        }
    }
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: new           #3                  // class jvm/bytecode/MethodInvoke
             3: dup
             4: invokespecial #4                  // Method "<init>":()V //构造方法
             7: astore_1
             8: aload_1
             9: invokespecial #5                  // Method test1:()V //私有方法
            12: aload_1
            13: invokespecial #6                  // Method test2:()V //final方法
            16: aload_1
            17: invokevirtual #7                  // Method test3:()V //普通方法
            20: aload_1
            21: pop
            22: invokestatic  #8                  // Method test4:()V //静态方法
            25: invokestatic  #8                  // Method test4:()V //静态方法
            28: aload_1
            29: invokevirtual #9                  // Method toString:()Ljava/lang/String;
            32: pop
            33: return
          LineNumberTable:
    
    • new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈
    • dup :是复制操作数栈栈顶的内容,本例即为【对象引用】,需要两份引用,一个是要配合 invokespecial 调用该对象的构造方法 "<init>":()V (会消耗掉栈顶一个引用),另一个要配合 astore_1 赋值给局部变量
    • 最终方法(final),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静态绑定
    • 普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态
    • 成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】
    • d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了
    • 执行 invokespecial 的情况是通过 super 调用父类方法

    七、多态

    /**
     * @Title: Demo.java
     * @Package jvm.bytecode
     * @Description: (用一句话描述该文件做什么)
     * @Author: hongcaixia
     * @Date: 2023/12/23 13:47
     * @Version V1.0
     * -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
     */
    public class Demo {
        public static void test(Animal animal) {
            animal.eat();
            System.out.println(animal.toString());
        }
    
        public static void main(String[] args) throws IOException {
            test(new Cat());
            test(new Dog());
            System.in.read();
        }
    }
    
    abstract class Animal {
        public abstract void eat();
    
        @Override
        public String toString() {
            return "我是" + this.getClass().getSimpleName();
        }
    }
    
    class Dog extends Animal {
    
        @Override
        public void eat() {
            System.out.println("狗吃肉");
        }
    }
    
    class Cat extends Animal {
    
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }
    

    1.停在 System.in.read() 方法上,这时运行 jps 获取进程 id

    hongcaixia@hongcaixiadeMacBook-Pro demo % jps
    95650 RemoteMavenServer36
    95637 RemoteMavenServer36
    62389 Launcher
    62390 Demo
    99993 RemoteMavenServer36
    65770 HSDB
    67372 Jps
    95951 RemoteMavenServer36
    1598 
    hongcaixia@hongcaixiadeMacBook-Pro demo % 
    
    

    2.运行 HSDB 工具

    进入jdk安装目录执行命令
    java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB

    hongcaixia@hongcaixiadeMacBook-Pro bytecode % cd /Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home
    hongcaixia@hongcaixiadeMacBook-Pro Home % java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
    Warning: the font "Times" is not available, so "Lucida Bright" has been substituted, but may have unexpected appearance or behavor. Re-enable the "Times" font to remove this warning.
    

    3.查找某个对象

    连上当前进程:62390
    File->Attach to hotspot process


    image.png

    打开 Tools -> Find Object By Query
    输入 select d fromjvm.bytecode.Dog d 点击 Execute 执行
    得到对象再内存中的地址

    4.查看对象内存结构

    点击超链接可以看到对象的内存结构,此对象没有任何属性,因此只有对象头的 16 字节,前 8 字节是MarkWord(哈希码和锁标记),后 8 字节就是对象的 Class 类型指针,但目前看不到它的实际地址。

    5.查看对象 Class 的内存地址

    可以通过 Windows -> Console 进入命令行模式,执行
    mem 0x00000001299b4978 2
    mem 有两个参数:

    • 参数 1 是对象地址
    • 参数 2 是查看 2 行(即 16 字节)

    结果中第二行 0x000000001b7d4028 即为 Class 的内存地址


    image.png image.png

    6.查看类的 vtable(多态的方法,虚方法表)

    方法1:Alt+R 进入 Inspector 工具,输入刚才的 Class 内存地址,看到如下界面
    方法2:或者 Tools -> Class Browser 输入 Dog 查找,可以得到相同的结果

    无论通过哪种方法,都可以找到 Dog Class 的 vtable 长度为 6,意思就是 Dog 类有 6 个虚方法(多态相关的,final,static 不会列入)
    那么这 6 个方法都是谁,从 Class 的起始地址开始算,偏移 0x1b8 就是 vtable 的起始地址,进行计算得到:

    0x000000001b7d4028
                   1b8 +
    ---------------------
    0x000000001b7d41e0
    

    通过 Windows -> Console 进入命令行模式,执行

    mem 0x000000001b7d41e0 6
    0x000000001b7d41e0: 0x000000001b3d1b10
    0x000000001b7d41e8: 0x000000001b3d15e8
    0x000000001b7d41f0: 0x000000001b7d35e8
    0x000000001b7d41f8: 0x000000001b3d1540
    0x000000001b7d4200: 0x000000001b3d1678
    0x000000001b7d4208: 0x000000001b7d3fa8
    

    就得到了 6 个虚方法的入口地址

    7.验证方法地址

    image.png

    通过 Tools -> Class Browser 查看每个类的方法定义,比较可知

    Dog - public void eat() @0x000000001b7d3fa8
    Animal - public java.lang.String toString() @0x000000001b7d35e8;
    Object - protected void finalize() @0x000000001b3d1b10;
    Object - public boolean equals(java.lang.Object) @0x000000001b3d15e8;
    Object - public native int hashCode() @0x000000001b3d1540;
    Object - protected native java.lang.Object clone() @0x000000001b3d1678;
    

    eat() 方法是 Dog 类自己的
    toString() 方法是继承 String 类的
    finalize() ,equals(),hashCode(),clone() 都是继承 Object 类的

    总结

    当执行 invokevirtual 指令时,

    1. 先通过栈帧中的对象引用找到对象
    2. 分析对象头,找到对象的实际 Class
    3. Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
    4. 查表得到方法的具体地址
    5. 执行方法的字节码

    八、异常

    1.单个catch

    public class ExceptionDemo {
    
        public static void main(String[] args) {
            int i = 0;
            try {
                i = 10;
            } catch (Exception e) {
                i = 20;
            }
        }
    }
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=3, args_size=1
             0: iconst_0
             1: istore_1
             2: bipush        10
             4: istore_1
             5: goto          12
             8: astore_2
             9: bipush        20
            11: istore_1
            12: return
          Exception table:
             from    to  target type
                 2     5     8   Class java/lang/Exception   //检测第2行到第5行的代码 (不包含5),如果发生了异常,进入第8行
          LineNumberTable:
            line 14: 0
            line 16: 2
            line 19: 5
            line 17: 8
            line 18: 9
            line 20: 12
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                9       3     2     e   Ljava/lang/Exception;
                0      13     0  args   [Ljava/lang/String;
                2      11     1     i   I
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 8
              locals = [ class "[Ljava/lang/String;", int ]
              stack = [ class java/lang/Exception ]
            frame_type = 3 /* same */
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "ExceptionDemo.java"
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % 
    

    可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置

    2.多个catch

    public class ManyExceptionDemo {
    
        public static void main(String[] args) {
            int i = 0;
            try {
                i = 10;
            } catch (ArithmeticException e) {
                i = 30;
            } catch (NullPointerException e) {
                i = 40;
            } catch (Exception e) {
                i = 50;
            }
        }
    }
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=3, args_size=1
             0: iconst_0
             1: istore_1
             2: bipush        10
             4: istore_1
             5: goto          26
             8: astore_2
             9: bipush        30
            11: istore_1
            12: goto          26
            15: astore_2
            16: bipush        40
            18: istore_1
            19: goto          26
            22: astore_2
            23: bipush        50
            25: istore_1
            26: return
          Exception table:
             from    to  target type
                 2     5     8   Class java/lang/ArithmeticException
                 2     5    15   Class java/lang/NullPointerException
                 2     5    22   Class java/lang/Exception
          LineNumberTable:
            line 14: 0
            line 16: 2
            line 23: 5
            line 17: 8
            line 18: 9
            line 23: 12
            line 19: 15
            line 20: 16
            line 23: 19
            line 21: 22
            line 22: 23
            line 24: 26
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                9       3     2     e   Ljava/lang/ArithmeticException;
               16       3     2     e   Ljava/lang/NullPointerException;
               23       3     2     e   Ljava/lang/Exception;
                0      27     0  args   [Ljava/lang/String;
                2      25     1     i   I
          StackMapTable: number_of_entries = 4
            frame_type = 255 /* full_frame */
              offset_delta = 8
              locals = [ class "[Ljava/lang/String;", int ]
              stack = [ class java/lang/ArithmeticException ]
            frame_type = 70 /* same_locals_1_stack_item */
              stack = [ class java/lang/NullPointerException ]
            frame_type = 70 /* same_locals_1_stack_item */
              stack = [ class java/lang/Exception ]
            frame_type = 3 /* same */
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "ManyExceptionDemo.java"
    hongcaixia@hongcaixiadeMacBook-Pro bytecode % 
    
    

    因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用

    3.multi-catch

    public class MultiCatchDemo {
    
        public static void main(String[] args) {
            try {
                Method test = MultiCatchDemo.class.getMethod("test");
                test.invoke(null);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
        public static void test() {
            System.out.println("ok");
        }
    }
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=2, args_size=1
             0: ldc           #2                  // class jvm/bytecode/MultiCatchDemo
             2: ldc           #3                  // String test
             4: iconst_0
             5: anewarray     #4                  // class java/lang/Class
             8: invokevirtual #5                  // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
            11: astore_1
            12: aload_1
            13: aconst_null
            14: iconst_0
            15: anewarray     #6                  // class java/lang/Object
            18: invokevirtual #7                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
            21: pop
            22: goto          30
            25: astore_1
            26: aload_1
            27: invokevirtual #11                 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
            30: return
          Exception table:
             from    to  target type
                 0    22    25   Class java/lang/NoSuchMethodException
                 0    22    25   Class java/lang/IllegalAccessException
                 0    22    25   Class java/lang/reflect/InvocationTargetException
          LineNumberTable:
            line 18: 0
            line 19: 12
            line 22: 22
            line 20: 25
            line 21: 26
            line 23: 30
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
               12      10     1  test   Ljava/lang/reflect/Method;
               26       4     1     e   Ljava/lang/ReflectiveOperationException;
                0      31     0  args   [Ljava/lang/String;
          StackMapTable: number_of_entries = 2
            frame_type = 89 /* same_locals_1_stack_item */
              stack = [ class java/lang/ReflectiveOperationException ]
            frame_type = 4 /* same */
        MethodParameters:
          Name                           Flags
          args
    
    

    九、finally

    public class FinallyDemo {
    
        public static void main(String[] args) {
            int i = 0;
            try {
                i = 10;
            } catch (Exception e) {
                i = 20;
            } finally {
                i = 30;
            }
        }
    }
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=4, args_size=1
             0: iconst_0 
             1: istore_1             // 0 -> i
             2: bipush        10.    // try 
             4: istore_1              // 10 -> i
             5: bipush        30    // finally 
             7: istore_1             // 30 -> i
             8: goto          27.   // return
            11: astore_2           // catch Exceptin -> e
            12: bipush        20
            14: istore_1          // 20 -> i
            15: bipush        30  // finally
            17: istore_1          // 30 -> i
            18: goto          27. // return 
            21: astore_3         // catch any -> slot 3
            22: bipush        30. // finally
            24: istore_1            // 30 -> i
            25: aload_3           // <- slot 3
            26: athrow             // throw 
            27: return
          Exception table:
             from    to  target type
                 2     5    11   Class java/lang/Exception
                 2     5    21   any    // 剩余的异常类型,比如 Error
                11    15    21   any   // 剩余的异常类型,比如 Error
          LineNumberTable:
            line 14: 0
            line 16: 2
            line 20: 5
            line 21: 8
            line 17: 11
            line 18: 12
            line 20: 15
            line 21: 18
            line 20: 21
            line 21: 25
            line 22: 27
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
               12       3     2     e   Ljava/lang/Exception;
                0      28     0  args   [Ljava/lang/String;
                2      26     1     i   I
          StackMapTable: number_of_entries = 3
            frame_type = 255 /* full_frame */
              offset_delta = 11
              locals = [ class "[Ljava/lang/String;", int ]
              stack = [ class java/lang/Exception ]
            frame_type = 73 /* same_locals_1_stack_item */
              stack = [ class java/lang/Throwable ]
            frame_type = 5 /* same */
        MethodParameters:
          Name                           Flags
          args
    }
    
    

    因为finally中的代码一定会被执行,所以 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程

    1.finally 出现了 return

    public class FinallyReturnDemo {
    
        public static void main(String[] args) {
            int result = test();
            System.out.println(result); //20
        }
    
        public static int test() {
            try {
                return 10;
            } finally {
                return 20;
            }
        }
    }
    
      public static int test();
        descriptor: ()I
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=2, args_size=0
             0: bipush        10      //<- 10 放入栈顶
             2: istore_0                // 10 -> slot 0 (从栈顶移除了)
             3: bipush        20     // <- 20 放入栈顶
             5: ireturn                 // 返回栈顶 int(20)
             6: astore_1            // catch any -> slot 1
             7: bipush        20   //<- 20 放入栈顶
             9: ireturn                //返回栈顶 int(20)
          Exception table:
             from    to  target type
                 0     3     6   any
          LineNumberTable:
            line 20: 0
            line 22: 3
          StackMapTable: number_of_entries = 1
            frame_type = 70 /* same_locals_1_stack_item */
              stack = [ class java/lang/Throwable ]
    }
    
    
    • 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 的为准
    • 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常
    public class FinallyReturnDemo {
    
        public static void main(String[] args) {
            int result = test();
            System.out.println(result); //20
        }
    
        public static int test() {
            try {
                int i = 1/0;
                return 10;
            } finally {
                return 20;
            }
        }
    }
    

    正常执行,打印20

    2.finally 对返回值影响

    public class FinallyReturnInfluDemo {
        public static void main(String[] args) {
            int result = test();
            System.out.println(result); //10
        }
    
        public static int test() {
            int i = 10;
            try {
                return i;
            } finally {
                i = 20;
            }
        }
    }
    
      public static int test();
        descriptor: ()I
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=3, args_size=0
             0: bipush        10     //<- 10 放入栈顶
             2: istore_0             //10 -> i
             3: iload_0             //<- i(10)
             4: istore_1            //10 -> slot 1,暂存至 slot 1,目的是为了固定返回值
             5: bipush        20 //<- 20 放入栈顶
             7: istore_0          //20 -> i
             8: iload_1           //<- slot 1(10) 载入 slot 1 暂存的值
             9: ireturn            //返回栈顶的 int(10)
            10: astore_2
            11: bipush        20
            13: istore_0
            14: aload_2
            15: athrow
          Exception table:
             from    to  target type
                 3     5    10   any
          LineNumberTable:
            line 18: 0
            line 20: 3
            line 22: 5
            line 20: 8
            line 22: 10
            line 23: 14
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                3      13     0     i   I
          StackMapTable: number_of_entries = 1
            frame_type = 255 /* full_frame */
              offset_delta = 10
              locals = [ int ]
              stack = [ class java/lang/Throwable ]
    }
    
    

    在return之前做了暂存(istore_1),所以finally块对try中的改变不会受影响。finally中没有return,异常可以正常抛出。

    十、Synchronized

    public class SynchronizedDemo {
    
        public static void main(String[] args) {
            Object lock = new Object();
            synchronized (lock) {
                System.out.println("ok");
            }
        }
    }
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=4, args_size=1
             0: new           #2      // class java/lang/Object    //new Object
             3: dup //复制一份 一份给构造方法用
             4: invokespecial #1   // Method java/lang/Object."<init>":()V //invokespecial <init>:()V
             7: astore_1        // lock引用 -> lock
             8: aload_1        // <- lock (synchronized开始)
             9: dup.    //复制一份 一份给加锁,一份给解锁
            10: astore_2      // lock引用 -> slot 2
            11: monitorenter // monitorenter(lock引用)
            12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
            15: ldc           #4                  // String ok
            17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            20: aload_2     // <- slot 2(lock引用)
            21: monitorexit  // monitorexit(lock引用)
            22: goto          30
            25: astore_3   // any -> slot 3
            26: aload_2    // <- slot 2(lock引用)
            27: monitorexit   // monitorexit(lock引用)
            28: aload_3
            29: athrow
            30: return
          Exception table:
             from    to  target type
                12    22    25   any
                25    28    25   any
          LineNumberTable:
            line 14: 0
            line 15: 8
            line 16: 12
            line 17: 20
            line 18: 30
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      31     0  args   [Ljava/lang/String;
                8      23     1  lock   Ljava/lang/Object;
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 25
              locals = [ class "[Ljava/lang/String;", class java/lang/Object, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
        MethodParameters:
          Name                           Flags
          args
    }
    
    

    方法级别的 synchronized 不会在字节码指令中有所体现

    相关文章

      网友评论

        本文标题:字节码指令

        本文链接:https://www.haomeiwen.com/subject/uvlfndtx.html