Java八股文 字节码

字节码指令学习

我们先来看Java代码

public class Test{
        public static void main(String[] args) {
                String s1 = "a";
                String s2 = "b";
                String s3 = "a" + "b";
                String s4 = s1 + s2;
                String s5 = "ab";
                String s6 = s4.intern();
        }
}

我们将它用javac指令编译成Test.class,再用javap -v指令查看详细反编译后的字节码详细信息

Classfile /usr/local/xhf/mianshi/test_StringTable/Test.class
  Last modified Sep 17, 2023; size 525 bytes
  MD5 checksum 4c34e0eb00af28d5c7c5911c27398f5a
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#20        // java/lang/Object."":()V
   #2 = String             #21            // a
   #3 = String             #22            // b
   #4 = String             #23            // ab
   #5 = Class              #24            // java/lang/StringBuilder
   #6 = Methodref          #5.#20         // java/lang/StringBuilder."":()V
   #7 = Methodref          #5.#25         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#26         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #27.#28        // java/lang/String.intern:()Ljava/lang/String;
  #10 = Class              #29            // Test
  #11 = Class              #30            // java/lang/Object
  #12 = Utf8               
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               SourceFile
  #19 = Utf8               Test.java
  #20 = NameAndType        #12:#13        // "":()V
  #21 = Utf8               a
  #22 = Utf8               b
  #23 = Utf8               ab
  #24 = Utf8               java/lang/StringBuilder
  #25 = NameAndType        #31:#32        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #26 = NameAndType        #33:#34        // toString:()Ljava/lang/String;
  #27 = Class              #35            // java/lang/String
  #28 = NameAndType        #36:#34        // intern:()Ljava/lang/String;
  #29 = Utf8               Test
  #30 = Utf8               java/lang/Object
  #31 = Utf8               append
  #32 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = Utf8               toString
  #34 = Utf8               ()Ljava/lang/String;
  #35 = Utf8               java/lang/String
  #36 = Utf8               intern
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=7, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: aload         4
        35: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
        38: astore        6
        40: return
      LineNumberTable:
        line 3: 0
        line 4: 3
        line 5: 6
        line 6: 9
        line 7: 29
        line 8: 33
        line 9: 40
}
SourceFile: "Test.java"

你说太长了?没事,我们一点点看


Classfile

Classfile /usr/local/xhf/mianshi/test_StringTable/Test.class
  Last modified Sep 17, 2023; size 525 bytes
  MD5 checksum 4c34e0eb00af28d5c7c5911c27398f5a
  Compiled from "Test.java"

Classfile信息:

  • 类文件路径:/usr/local/xhf/mianshi/test_StringTable/Test.class
  • 最后修改日期:Sep 17, 2023
  • 文件大小:525字节
  • MD5校验和:4c34e0eb00af28d5c7c5911c27398f5a
  • 编译来源:Test.java

public class Test

public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER

类声明:

  • 类的声明:public class Test
  • 次版本号:0
  • 主版本号:52
  • 类标志(flags):ACC_PUBLIC, ACC_SUPER

Constant pool

Constant pool:
#...

常量池中的内容太多,现在只是大概介绍一下,详细信息解释后续字节码会有涉及

常量池:

  • 常量池中包含了一系列的常量,包括方法引用、字符串、类名和字段等。常量>池中的#1到#36是各种常量的引用,用于在字节码中引用这些常量

public Test();

  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 1: 0

public Test();
表示空参构造函数的声明

  • descriptor: ()V
    构造函数的描述符,()表示无参数,V表示返回类型为 void
  • flags: ACC_PUBLIC
    访问标志,表示构造函数是公共的(ACC_PUBLIC),即可以从其他类中访问
  • Code:
    表示字节码的开始,以下内容是字节码指令
  • stack=1, locals=1, args_size=1
    字节码堆栈的信息。stack=1 表示操作数栈的深度为1,locals=1 表示本地变量表中有一个本地变量【this引用】,args_size=1 表示方法的参数数量为1【隐式的 this 参数】
  • 0: aload_0
    将当前对象的引用加载到操作数栈上
  • 1: invokespecial #1
    使用invokespecial指令,而#1则表示方法的符号引用。这个方法具体的含义是:Method java/lang/Object."":()V,也就是反编译的字节码中用//注释的那句话,这句话是程序添加上去的.
    • 现在让我们解释这句话的含义: java/lang/Object类的构造函数().这个构造函数没有参数,所以()中没有内容,:()V表示它返回void
    • 那么,程序是如何判断这条指令含义呢?答案是查询constant pool.
      #1可知,该方法的符号引用是constant pool#1所在位置
      #1 = Methodref #11.#20 // java/lang/Object."":()V
    • 继续查表#11.#20得到如下信息
      #11 = Class #30 // java/lang/Object 【一个类,具体指向的是Object类】
      • #30 = Utf8 java/lang/Object 【UTF-8编码的字符串常量,内容是 “java/lang/Object”】
    • #20 = NameAndType #12:#13 // "":()V 【表示方法的名称和类型,名称是,表示构造函数;类型是:()V,表示无参数无返回值】
      • #12 = Utf8 【一个UTF-8编码的字符串常量,指向字符串""】
      • #13 = Utf8 ()V 【一个UTF-8编码的字符串常量,指向字符串"()V"】
    • 将上述查表得到的所有细腻些汇总,即可得到 #1 = Methodref的含义,也就是//后面的解释内容
  • 4: return
    表示方法的结束,4 是指令的位置或偏移量,表示这是方法字节码的第四行指令(从0开始计数)
  • LineNumberTable:
    字节码指令源代码中的行号进行映射
    • line 1: 0
      (line 1)对应于源代码中的第 0 行(0)。这意味着构造函数的第一条字节码指令 0: aload_0 在源代码的第一行

public static void main(java.lang.String[]);

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=7, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: aload         4
        35: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
        38: astore        6
        40: return
      LineNumberTable:
        line 3: 0
        line 4: 3
        line 5: 6
        line 6: 9
        line 7: 29
        line 8: 33
        line 9: 40
}
  • stack=2, locals=7, args_size=1
    • 栈的深度为2,第一个栈用于存放public Test()构造函数,第二个栈用于存放main方法;
    • locals表示本地变量表中存在7个变量【main方法中new的变量:s1~s6 + String args[]】
    • args_size=1表示方法参数列表中存在一个变量【String args[]】
  • 0: ldc #2 // String a
    • 0表示这是字节码的第一行指令
    • ldc 表示load constant,加载常量到操作数栈上
    • #2表示在constant pool中查询的位置,查询得到的结果为
      • #2 = String #21 // a
      • #21 = Utf8 a
        综合起来为String a,即将String a加载到栈中
  • 2: astore_1
    • 操作数栈上的引用值a存储到局部变量表中第一个本地变量槽位
  • 3: ldc #3 // String b
    • 将String b 加载到栈中
  • 5: astore_2
    • 将b存储到局部变量表中的第二个本地变量槽位
  • 6: ldc #4 // String ab
    • 将String ab加载到栈
  • 8: astore_3
    • 将String ab加载到本地变量
  • 9: new #5 // class java/lang/StringBuilder
    • #5 = Class #24 // java/lang/StringBuilder
    • #24 = Utf8 java/lang/StringBuilder
      创建一个新的 java.lang.StringBuilder 对象的实例,并将其引用推入操作数栈
  • 12: dup
    • 复制栈顶元素并将副本压入操作数栈
  • 13: invokespecial #6 // Method java/lang/StringBuilder.“”: ()V
    • 调用StringBuilder的构造方法
      也就是new StringBuilder()
  • 16: aload_1
    • 局部变量表第一个本地变量槽位的值加载到操作数栈上,也就是String a
  • 17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    • virtual 表示这是一次虚拟方法调用,也就是调用一个非静态方法
    • invokevirtual 是指令的助记符,表示调用一个虚拟方法
    • #7 表示常量池中的索引号,即方法的符号引用在常量池中的位置。具体的查询过程不在赘述,结果为java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      也就是.appen(a)
  • 20: aload_2
    • 加载String b到操作数栈
  • 21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    • .append(b)
      24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    • .toString(),将9-21部指令结合在一起,得到new StringBuilder().append("a").append("b").toString();
  • 27: astore 4
    • 将操作数栈上的值存储到局部变量表中的第四个本地变量槽位,也就是ab
  • 29: ldc #4 // String ab
    • 将ab加载到操作数栈中
  • 31: astore 5
    • 存储字符串到局部变量表的第五个本地变量 5 中
  • 33: aload 4
    • 加载局部变量表中的第四个本地变量 4(先前创建的字符串 “ab”)到操作数栈上。
  • 35: invokevirtual #9 // Method java/lang/String.intern:()Ljava/lang/String;
    • 调用字符串的 intern 方法,将其放入字符串常量池中。
  • 38: astore 6
    • 存储在字符串常量池中的字符串引用到局部变量表的第六个本地变量 6 中。

指令辨析

  • ldc 指令:
    ldc 是 “load constant” 的缩写,它用于将常量加载到操作数栈上。
  • aload 指令:
    aload 是 “load reference” 的缩写,它用于加载引用类型的值(通常是对象引用)到操作数栈上
  • astore 指令:
    astore 是 "store reference"的缩写,它用于将数据从栈上保存到方法的局部变量中

你可能感兴趣的:(java,开发语言,字节码,javap,-v)