@IT·互联网

字节码指令

2024-01-05  本文已影响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:将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有:

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

image.png

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

image.png

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

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:将slot 1的值读取到操作数栈

image.png

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

image.png

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

image.png

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

image.png

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

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

image.png
image.png

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

image.png

invokevirtual #5

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 % 

分析:

int a = 10;:

bipush 10:把10放入操作数栈


image.png

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


image.png

a++:

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


image.png

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


image.png

+ ++a:

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


image.png

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


image.png

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


image.png

+ a--

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


image.png

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


image.png

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


image.png

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

三、条件判断指令

指令 助记符 含义
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
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 % 

四、循环控制指令

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:

七、多态

/**
 * @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 有两个参数:

结果中第二行 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 ]
}

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 不会在字节码指令中有所体现

上一篇下一篇

猜你喜欢

热点阅读