Java bytecode study notes (2): How to read Java bytecode?

1. Bytecode viewing method

    The .class file itself is a binary bytecode, which is too obscure to read directly. When we look at it here, we use some disassembly tools to view it.

1.1、javap

    javapCan decompile bytecode files. You can understand the basic usage of javap through javap -helpthe command .

D:\workspace\DemoTest\out\production\DemoTest\com\leo\test>javap -help
用法: javap <options> <classes>
其中, 可能的选项包括:
  -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置

1.2、jclasslib

    jclasslib Bytecode ViewerIt is a plug-in in the IDEA development tool, which can easily view the compiled bytecode file of each java class. There are many specific installation methods, so I won't repeat them here.
jclasslib plugin

2. Bytecode analysis

2.1. Code before compilation

package com.leo.test;

public class ByteCodeTest {
    
    
    public static int num = 1;

    public static int add(byte a, short b, int c, String d, float e, double f) {
    
    
        int d_int = Integer.parseInt(d);
        return (int) (a + b + c + d_int + e + f);
    }

    public static void main(String[] args) {
    
    
        byte a = (byte) 0xFF; // -1
        short b = 2;
        int c = 3;
        String d = "4";
        float e = 5.0f;
        double f = 6.0d;
        boolean bool = false;
        int g = add(a, b, c, d, e, f);
        System.out.println(num); // 1
        System.out.println(g); // 19
    }
}

2.2. After compilation

Use the javap command to view the bytecode:

javap -c -verbose ByteCodeTest.class

The content of the decompiled file using javap is as follows:

Classfile /D:/workspace/DemoTest/out/production/DemoTest/com/leo/test/ByteCodeTest.class
  Last modified 2022-6-28; size 1136 bytes
  MD5 checksum 6550d8d70ee59d88e1ec1614d1b6f1e1
  Compiled from "ByteCodeTest.java"
public class com.leo.test.ByteCodeTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#46        // java/lang/Object."<init>":()V
   #2 = Methodref          #47.#48        // java/lang/Integer.parseInt:(Ljava/lang/String;)I
   #3 = String             #49            // 4
   #4 = Float              5.0f
   #5 = Double             6.0d
   #7 = Methodref          #11.#50        // com/leo/test/ByteCodeTest.add:(BSILjava/lang/String;FD)I
   #8 = Fieldref           #51.#52        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Fieldref           #11.#53        // com/leo/test/ByteCodeTest.num:I
  #10 = Methodref          #54.#55        // java/io/PrintStream.println:(I)V
  #11 = Class              #56            // com/leo/test/ByteCodeTest
  #12 = Class              #57            // java/lang/Object
  #13 = Utf8               num
  #14 = Utf8               I
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcom/leo/test/ByteCodeTest;
  #22 = Utf8               add
  #23 = Utf8               (BSILjava/lang/String;FD)I
  #24 = Utf8               a
  #25 = Utf8               B
  #26 = Utf8               b
  #27 = Utf8               S
  #28 = Utf8               c
  #29 = Utf8               d
  #30 = Utf8               Ljava/lang/String;
  #31 = Utf8               e
  #32 = Utf8               F
  #33 = Utf8               f
  #34 = Utf8               D
  #35 = Utf8               d_int
  #36 = Utf8               main
  #37 = Utf8               ([Ljava/lang/String;)V
  #38 = Utf8               args
  #39 = Utf8               [Ljava/lang/String;
  #40 = Utf8               bool
  #41 = Utf8               Z
  #42 = Utf8               g
  #43 = Utf8               <clinit>
  #44 = Utf8               SourceFile
  #45 = Utf8               ByteCodeTest.java
  #46 = NameAndType        #15:#16        // "<init>":()V
  #47 = Class              #58            // java/lang/Integer
  #48 = NameAndType        #59:#60        // parseInt:(Ljava/lang/String;)I
  #49 = Utf8               4
  #50 = NameAndType        #22:#23        // add:(BSILjava/lang/String;FD)I
  #51 = Class              #61            // java/lang/System
  #52 = NameAndType        #62:#63        // out:Ljava/io/PrintStream;
  #53 = NameAndType        #13:#14        // num:I
  #54 = Class              #64            // java/io/PrintStream
  #55 = NameAndType        #65:#66        // println:(I)V
  #56 = Utf8               com/leo/test/ByteCodeTest
  #57 = Utf8               java/lang/Object
  #58 = Utf8               java/lang/Integer
  #59 = Utf8               parseInt
  #60 = Utf8               (Ljava/lang/String;)I
  #61 = Utf8               java/lang/System
  #62 = Utf8               out
  #63 = Utf8               Ljava/io/PrintStream;
  #64 = Utf8               java/io/PrintStream
  #65 = Utf8               println
  #66 = Utf8               (I)V
{
    
    
  public static int num;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  public com.leo.test.ByteCodeTest();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/leo/test/ByteCodeTest;

  public static int add(byte, short, int, java.lang.String, float, double);
    descriptor: (BSILjava/lang/String;FD)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=8, args_size=6
         0: aload_3
         1: invokestatic  #2                  // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I
         4: istore        7
         6: iload_0
         7: iload_1
         8: iadd
         9: iload_2
        10: iadd
        11: iload         7
        13: iadd
        14: i2f
        15: fload         4
        17: fadd
        18: f2d
        19: dload         5
        21: dadd
        22: d2i
        23: ireturn
      LineNumberTable:
        line 7: 0
        line 8: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0     a   B
            0      24     1     b   S
            0      24     2     c   I
            0      24     3     d   Ljava/lang/String;
            0      24     4     e   F
            0      24     5     f   D
            6      18     7 d_int   I

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=7, locals=10, args_size=1
         0: iconst_m1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iconst_3
         5: istore_3
         6: ldc           #3                  // String 4
         8: astore        4
        10: ldc           #4                  // float 5.0f
        12: fstore        5
        14: ldc2_w        #5                  // double 6.0d
        17: dstore        6
        19: iconst_0
        20: istore        8
        22: iload_1
        23: iload_2
        24: iload_3
        25: aload         4
        27: fload         5
        29: dload         6
        31: invokestatic  #7                  // Method add:(BSILjava/lang/String;FD)I
        34: istore        9
        36: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        39: getstatic     #9                  // Field num:I
        42: invokevirtual #10                 // Method java/io/PrintStream.println:(I)V
        45: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        48: iload         9
        50: invokevirtual #10                 // Method java/io/PrintStream.println:(I)V
        53: return
      LineNumberTable:
        line 12: 0
        line 13: 2
        line 14: 4
        line 15: 6
        line 16: 10
        line 17: 14
        line 18: 19
        line 19: 22
        line 20: 36
        line 21: 45
        line 22: 53
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      54     0  args   [Ljava/lang/String;
            2      52     1     a   B
            4      50     2     b   S
            6      48     3     c   I
           10      44     4     d   Ljava/lang/String;
           14      40     5     e   F
           19      35     6     f   D
           22      32     8  bool   Z
           36      18     9     g   I

  static {
    
    };
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_1
         1: putstatic     #9                  // Field num:I
         4: return
      LineNumberTable:
        line 4: 0
}
SourceFile: "ByteCodeTest.java"

2.3. Bytecode structure

insert image description here

The bytecode structure given by Oracle official is quoted as follows:

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];
}

explain

type name explain length quantity
u4 magic magic number 4 bytes 1
u2 minor_version minor version number 2 bytes 1
u2 major_version major version number 2 bytes 1
u2 constant_pool_count constant pool size 2 bytes 1
cp_info constant_pool[] constant pool n bytes costant_pool_count - 1
u2 access_flags class access control permissions 2 bytes 1
u2 this_class class index 2 bytes 1
u2 super_class parent class index 2 bytes 1
u2 interfaces_count Number of Interface Indexes 2 bytes 1
u2 interfaces[] Implemented interface content 2 bytes interfaces_count
u2 fields_count Number of member properties 2 bytes 1
field_info fields[] member attribute value n bytes fields_count
u2 methods_count number of methods 2 bytes 1
method_info methods[] method table n bytes methods_count
u2 attributes_count number of class attributes 2 bytes 1
attribute_info attributes[] class attribute value n bytes attributes_count
  • Unsigned numbers are the most basic data types . It uses u1, u2, u4, and u8 to represent unsigned numbers of 1 byte, 2 bytes, 4 bytes, and 8 bytes respectively. Unsigned numbers can be used to describe numbers, index references, quantity values, or string values ​​formed according to the UTF-8 encoding. For example, u4 in the first line of the following table means that the first 4 bytes of the Class file represent the magic number of the file, and u2 in the second line means that the 5th to 6th bytes of the Class file represent the minor version number of the JDK.

  • A table is a composite data type composed of multiple unsigned numbers or other tables as data items . All tables habitually end with _info. Tables are used to describe data with a composite structure with hierarchical relationships. For example, row 5 of the following table indicates a table (constant pool) of type cp_info, which stores all constants of this class.

Borrow a schematic diagram of the bytecode structure organization of a Class file :
Organization diagram

2.4. Simplified understanding of bytecode structure

2.4.1, Class file structure

    A typical class file is divided into ten parts: MagicNumber, Version, Constant_pool, Access_flag, This_class, Super_class, Interfaces, Fields, Methods and Attributes.
    But for the convenience of understanding, the simplified model is borrowed: Class摘要, 常量池,方法栈帧

2.4.2, Class Summary

    The summary is mainly to record some basic information of the class, including the size of the class, modification time, checksum, JDK version information, access range of the class, etc.

D:\workspace\DemoTest\out\production\DemoTest\com\leo\test>javap -v ByteCodeTest.class // 反编译字节码文件 ByteCodeTest.class
Classfile /D:/workspace/DemoTest/out/production/DemoTest/com/leo/test/ByteCodeTest.class // 文件来源,可以看到文件的路径
  Last modified 2022-6-28; size 1136 bytes // 类最后修改时间、类大小
  MD5 checksum 6550d8d70ee59d88e1ec1614d1b6f1e1 // 校验和
  Compiled from "ByteCodeTest.java"	// 编译的文件
public class com.leo.test.ByteCodeTest // 全类名
  minor version: 0 // jdk 子版本号
  major version: 52 // jdk 主版本号,52 代表的就是 jdk1.8
  flags: ACC_PUBLIC, ACC_SUPER // 说明这个类是 public 类,初始化方法使用父类的

modifier meaning

2.4.3. Constant pool

  • Literal quantity : including text strings, final modified member variables, and data values, etc. For constants of the basic numeric int type, the constant pool value saves references and literal names, but does not save data values
   #3 = String             #49            // 4,引用的是 #49 行的值
   #4 = Float              5.0f			  // float 类型 5.0
   #5 = Double             6.0d           // double 类型 6.0
   #28 = Utf8               c             // int 类型只保存了名称,没有存实际值
   ......中间省略了
   #49 = Utf8               4
  • Symbolic references : names and descriptions of classes and interfaces
   #7 = Methodref          #11.#50        // com/leo/test/ByteCodeTest.add:(BSILjava/lang/String;FD)I
   #8 = Fieldref           #51.#52        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Fieldref           #11.#53        // com/leo/test/ByteCodeTest.num:I
  #10 = Methodref          #54.#55        // java/io/PrintStream.println:(I)V
  #11 = Class              #56            // com/leo/test/ByteCodeTest
  #12 = Class              #57            // java/lang/Object
  #57 = Utf8               java/lang/Object
   ......中间省略了
  #50 = NameAndType        #22:#23        // add:(BSILjava/lang/String;FD)I
  #51 = Class              #61            // java/lang/System
  #52 = NameAndType        #62:#63        // out:Ljava/io/PrintStream;
  #53 = NameAndType        #13:#14        // num:I
  #54 = Class              #64            // java/io/PrintStream
  #55 = NameAndType        #65:#66        // println:(I)V

2.4.4, method stack frame

The two methods add and main methods given in the code sample:

  public static int add(byte, short, int, java.lang.String, float, double);
    descriptor: (BSILjava/lang/String;FD)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=8, args_size=6
         0: aload_3
         1: invokestatic  #2                  // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I
         4: istore        7
         6: iload_0
         7: iload_1
         8: iadd
         9: iload_2
        10: iadd
        11: iload         7
        13: iadd
        14: i2f
        15: fload         4
        17: fadd
        18: f2d
        19: dload         5
        21: dadd
        22: d2i
        23: ireturn
      LineNumberTable:
        line 7: 0
        line 8: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0     a   B
            0      24     1     b   S
            0      24     2     c   I
            0      24     3     d   Ljava/lang/String;
            0      24     4     e   F
            0      24     5     f   D
            6      18     7 d_int   I

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=7, locals=10, args_size=1
         0: iconst_m1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iconst_3
         5: istore_3
         6: ldc           #3                  // String 4
         8: astore        4
        10: ldc           #4                  // float 5.0f
        12: fstore        5
        14: ldc2_w        #5                  // double 6.0d
        17: dstore        6
        19: iconst_0
        20: istore        8
        22: iload_1
        23: iload_2
        24: iload_3
        25: aload         4
        27: fload         5
        29: dload         6
        31: invokestatic  #7                  // Method add:(BSILjava/lang/String;FD)I
        34: istore        9
        36: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        39: getstatic     #9                  // Field num:I
        42: invokevirtual #10                 // Method java/io/PrintStream.println:(I)V
        45: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        48: iload         9
        50: invokevirtual #10                 // Method java/io/PrintStream.println:(I)V
        53: return
      LineNumberTable:
        line 12: 0
        line 13: 2
        line 14: 4
        line 15: 6
        line 16: 10
        line 17: 14
        line 18: 19
        line 19: 22
        line 20: 36
        line 21: 45
        line 22: 53
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      54     0  args   [Ljava/lang/String;
            2      52     1     a   B
            4      50     2     b   S
            6      48     3     c   I
           10      44     4     d   Ljava/lang/String;
           14      40     5     e   F
           19      35     6     f   D
           22      32     8  bool   Z
           36      18     9     g   I

2.4.4.1, stack frame summary

// add 方法
  public static int add(byte, short, int, java.lang.String, float, double);
    descriptor: (BSILjava/lang/String;FD)I // 返回值 I 指的是 int 类型
    flags: ACC_PUBLIC, ACC_STATIC // 公共方法、静态方法
    Code:
      stack=4, locals=8, args_size=6 // 栈帧深度是4,最大局部变量8个,入参个数6个

// main 方法
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V // 返回值 V 指的是 void 类型
    flags: ACC_PUBLIC, ACC_STATIC // 公共方法、静态方法
    Code:
      stack=7, locals=10, args_size=1 // 栈帧深度是7,最大局部变量10个,入参个数1个

2.4.4.2, local variable table

    The local variable table is an array used to store the local variables of the current method. The types that can be stored in the table include boolean, byte, char, short, int, float, and reference types. Because local variables are private to threads, there will be no For thread safety issues, the size of the local variable table in each stack frame is determined after the .class bytecode file is compiled.

// add 方法
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0     a   B
            0      24     1     b   S
            0      24     2     c   I
            0      24     3     d   Ljava/lang/String;
            0      24     4     e   F
            0      24     5     f   D
            6      18     7 d_int   I

// main 方法
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      54     0  args   [Ljava/lang/String; // 入参是 args ,类型是 String 数组
            2      52     1     a   B
            4      50     2     b   S
            6      48     3     c   I
           10      44     4     d   Ljava/lang/String; // 局部变量,类型是 String
           14      40     5     e   F
           19      35     6     f   D
           22      32     8  bool   Z
           36      18     9     g   I

    In the JVM specification, each variable/field has descriptive information. The main function of the descriptive information is to describe the data type of the field, the parameter list (including quantity, type and order) and the return value of the method. According to the descriptor rules, the basic data Both the type and the void type representing no return value are represented by an uppercase character, and the object type is represented by the character L plus the fully qualified name of the object, in order to compress the size of the bytecode file. For basic data types, the JVM uses only one uppercase letter to represent them, as follows:
identifier

2.4.4.3, operand stack

# main方法的操作数栈:
         0: iconst_m1                       // 将 int 类型常量 -1 压入栈, 这个就是 a 的值, byte 操作的时候会补齐高位按照 int 值操作
         1: istore_1                        // 将 int 类型值存入局部变量 1 
         2: iconst_2                        // 将 int 类型常量 2 压入栈
         3: istore_2                        // 将 int 类型值存入局部变量 2
         4: iconst_3                        // 将 int 类型常量 3 压入栈
         5: istore_3                        // 将 int 类型值存入局部变量 3
         6: ldc           #3                // String 4, 将常量池中的项压入栈
         8: astore        4                 // 将引用类型值存入局部变量 4
        10: ldc           #4                // float 5.0f, 把常量池中的项压入栈 
        12: fstore        5                 // 将 float 类型值存入局部变量 5
        14: ldc2_w        #5                // double 6.0d, 把常量池中的 double 类型的项压入栈(使用宽索引,long类型也是)
        17: dstore        6					// 将 double 类型值存入局部变量 6
        19: iconst_0						// 将 int 类型常量 0 压入栈,boolean 的值只有两个: true=1, false=0
        20: istore        8                 // 将 int 类型值存入局部变量 8
        22: iload_1							// 从局部变量 1 中装载 int 类型值
        23: iload_2							// 从局部变量 2 中装载 int 类型值
        24: iload_3                         // 从局部变量 3 中装载 int 类型值
        25: aload         4                 // 从局部变量 4 中装载 引用 类型值
        27: fload         5                 // 从局部变量 5 中装载 float 类型值
        29: dload         6					// 从局部变量 6 中装载 double 类型值
        31: invokestatic  #7                // Method add:(BSILjava/lang/String;FD)I, 调用类静态方法 add
        34: istore        9					// 将 int 类型值存入局部变量
        36: getstatic     #8                // Field java/lang/System.out:Ljava/io/PrintStream;, 从类中获取静态字段
        39: getstatic     #9                // Field num:I, 从类中获取静态字段 num 类型为 int
        42: invokevirtual #10               // Method java/io/PrintStream.println:(I)V, 调度对象的实现方法
        45: getstatic     #8                // Field java/lang/System.out:Ljava/io/PrintStream;, 从类中获取静态字段
        48: iload         9					// 从局部变量 9 中装载 int 类型值
        50: invokevirtual #10               // Method java/io/PrintStream.println:(I)V,, 调度对象的实现方法
        53: return							// 从方法中返回,返回值为void

Classification of instructions:

  • According to the nature of the instruction, it can be mainly divided into four types:
  • Stack manipulation instructions, including those that interact with local variables
  • Program Flow Control Instructions
  • Object manipulation instructions, including method call instructions
  • Arithmetic operations and type conversion instructions

Common commands:

  • [ Local variable stack instruction ] Load a local variable to the operand stack: xload, xload_(where x is i, l, f, d, a, n is 0 to 3)
  • [ Constant stack instruction ] Load a constant into the operand stack: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_ml, iconst, lconst, fconst, dconst
  • [ Pop stack and load local variable table instruction ] Store a value from the operand stack to the local variable table: xstore, xstore_ (where x is i, l, f, d, a, n is 0 to 3); xastore (where x is i, 1, f, d, a, b, c, s)

    Some of the instruction mnemonics listed above end with angle brackets (such as iload). These instruction mnemonics actually represent a group of instructions (for example, iload represents the instructions iload0, iload1, iload2, and iload3). These groups of instructions are special forms of a general instruction (such as iload) with one operand. For these groups of special instructions, they seem to have no operands and do not need to perform the action of fetching operands, but The operands are all implicit in the instruction.

iload_0: Push the data at index 0 in the local variable table into the operand stack.
iload 0: Push the data at index 0 in the local variable table into the operand stack.
iload 4: Push the data at index 4 in the local variable table into the operand stack.

    The meanings expressed by the first two are the same, but iload_0 is equivalent to only the opcode, so it only occupies 1 byte, while iload 0 is composed of the opcode and the operand, and the operand occupies 2 bytes, so it occupies 3 bytes.

For more instructions, please refer to " JVM Assembly Instruction Stack and Local Variable Operation "

3. Summary

    This article is just a brief understanding of the bytecode viewing method, the structure of the bytecode file, and how the bytecode runs. The relationship between threads and stack frames is as follows (network diagram, to help understand):

Stacks and Stack Frames

Finally, refer to the calling process between threads and stack frames on the Internet:

Thread Creation -> Thread Stack Creation -> Call Stack Frame A -> Stack Frame A Creation -> Stack Frame A Call Operand Stack -> Stack Frame A Operand Stack Call Stack Frame B -> Stack Frame B Creation -> Stack Frame B calls operand stack -> stack frame B returns -> stack frame B destroys -> stack frame A returns -> stack frame A destroys -> thread stack destroys -> thread destroys

  1. thread creation
  2. thread stack creation
  3. call stack frame A
  4. Stack frame A is created
  5. Stack frame A call operand stack
  6. Stack frame A operand stack call stack frame B
  7. Stack frame B is created
  8. Stack frame B call operand stack
  9. Stack frame B returns
  10. Stack frame B is destroyed
  11. Stack frame A returns
  12. Stack frame A is destroyed
  13. thread stack destruction
  14. thread destruction

article citation

https://blog.csdn.net/xidianzxm/article/details/108634553 (operating principle of thread-stack frame illustration/multi-thread stack frame)
https://blog.csdn.net/Mr_YarNell/article/details/118482034 ( Java bytecode (Java bytecode))

Guess you like

Origin blog.csdn.net/u011047968/article/details/125502318