JVM字节码(class文件)解析

简介: JVM字节码(class文件)解析

JVM就是Java虚拟机,它是Java程序运行的载体。
计算机只识别0和1。Java是⾼级语⾔。⾼级语⾔编写的程序要想被计算机执⾏,需要变成⼆进制形式的本地机器码。能直接变成机器码的语义是C++,它的缺点是不同操作系统,需要准备多份。Java需要先变成Java字节码(class⽂件)。然后再变成机器码。JVM可以实现Java的⼀次编译,到处运⾏。
这个就是区别于类似于C语⾔的⽅式。机器码是电脑CPU直接读取运行的机器指令,运行速度最快,但是非常晦涩难懂,也比较难编写,一般从业人员接触不到。字节码是一种中间状态(中间码)的二进制代码(文件)。需要直译器转译后才能成为机器码。

JVM字节码(class文件)

对于程序本身的优化,可以借鉴很多前辈们的经验,但是有些时候,在从源码角度方面分析的话,不好鉴别出哪个效率高,如对字符串拼接的操作,是直接“+”号拼接效率高还是使用StringBuilder效率高?这个时候,就需要通过查看编译好的class文件中字节码,就可以找到答案。
我们都知道,java编写应用,需要先通过javac命令编译成class文件,再通过jvm执行,jvm执行时是需要将class文件中的字节码载入到jvm进行运行的。

通过javap命令查看class文件的字节码内容

首先,看一个简单的Test1类的代码:

public class Test1 {

    public static void main(String[] args) {
        int a = 2;
        int b = 5;
        int c = b - a;
        System.out.println(c);
    }
}

通过javap命令查看class文件中的字节码内容:

javap ‐v Test1.class > Test1.txt

用法: 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>    覆盖引导类文件的位置

查看Test1.txt文件,内容如下:

Classfile /E:cn/zjq/jvm/Test1.class
  Last modified 2021-7-28; size 411 bytes
  MD5 checksum 50e6a3429b5ebc3fa0ad40dcb54ebf03
  Compiled from "Test1.java"
public class cn.zjq.jvm.Test1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // cn/zjq/jvm/Test1
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Test1.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               cn/zjq/jvm/Test1
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public cn.zjq.jvm.Test1();
    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

  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: iconst_2
         1: istore_1
         2: iconst_5
         3: istore_2
         4: iload_2
         5: iload_1
         6: isub
         7: istore_3
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: iload_3
        12: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        15: return
      LineNumberTable:
        line 6: 0
        line 7: 2
        line 8: 4
        line 9: 8
        line 10: 15
}
SourceFile: "Test1.java"

内容大致分为4个部分:
第一部分:显示了生成这个class的java源文件、版本信息、生成时间等。
第二部分:显示了该类中所涉及到常量池,共26个常量。
第三部分:显示该类的构造器,编译器自动插入的。
第四部分:显示了main方的信息。(这个是需要我们重点关注的)

常量池

官网文档:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4-140

Constant Type Value 说明
CONSTANT_Class 7 类或接口的符号引用
CONSTANT_Fieldref 9 字段的符号引用
CONSTANT_Methodref 10 类中方法的符号引用
CONSTANT_InterfaceMethodref 11 接口中方法的符号引用
CONSTANT_String 8 字符串类型常量
CONSTANT_Integer 3 整形常量
CONSTANT_Float 4 浮点型常量
CONSTANT_Long 5 长整型常量
CONSTANT_Double 6 双精度浮点型常量
CONSTANT_NameAndType 12 字段或方法的符号引用
CONSTANT_Utf8 1 UTF-8编码的字符串
CONSTANT_MethodHandle 15 表示方法句柄
CONSTANT_MethodType 16 标志方法类型
CONSTANT_InvokeDynamic 18 表示一个动态方法调用点

描述符

字段描述符

官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2

| FieldType_ _term |

Type |

Interpretation
B byte signed byte

C |

char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
| D | double | double-precision floating-point value |
| F | float | single-precision floating-point value |
| I | int | integer |
| J | long | long integer |
| LClassName; | reference | an instance of class ClassName |
| S | short | signed short |
| Z | boolean | true or false |
| [ | reference | one array dimension |

方法描述符

官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3
示例:
The method descriptor for the method:

Object m(int i, double d, Thread t) {...}

is:

(IDLjava/lang/Thread;)Ljava/lang/Object;

解读方法字节码

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V //方法描述,V表示该方法的放回值为void
flags: ACC_PUBLIC, ACC_STATIC // 方法修饰符,public、static的
Code:
// stack=2,操作栈的大小为2、locals=4,本地变量表大小,args_size=1, 参数的个数
stack=2, locals=4, args_size=1
0: iconst_2 //将数字2值压入操作栈,位于栈的最上面
1: istore_1 //从操作栈中弹出一个元素(数字2),放入到本地变量表中,位于下标为1的位置(下标为0的是this)
2: iconst_5 //将数字5值压入操作栈,位于栈的最上面
3: istore_2 //从操作栈中弹出一个元素(5),放入到本地变量表中,位于第下标为2个位置
4: iload_2 //将本地变量表中下标为2的位置元素压入操作栈(5)
5: iload_1 //将本地变量表中下标为1的位置元素压入操作栈(2)
6: isub //操作栈中的2个数字相减
7: istore_3 // 将相减的结果压入到本地本地变量表中,位于下标为3的位置
// 通过#2号找到对应的常量,即可找到对应的引用
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_3 //将本地变量表中下标为3的位置元素压入操作栈(3)
// 通过#3号找到对应的常量,即可找到对应的引用,进行方法调用
12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
15: return //返回
LineNumberTable: //行号的列表
line 6: 0
line 7: 2
line 8: 4
line 9: 8
line 10: 15 LocalVariableTable: // 本地变量表

| Start
0 | Length
16 | Slot
0 | Name
args | Signature

[Ljava/lang/String;
2 14 1 a I
4 12 2 b I
8 8 3 c I
}
SourceFile: "Test1.java"

图解

image.png
image.png
image.png

研究i++ 与++i 的不同

我们都知道,i++表示,先返回再+1,++i表示,先+1再返回。它的底层是怎么样的呢? 我们一起探究下。
编写测试代码:

public class Test2 {
    public static void main(String[] args) {
        new Test2().method1(); 
        new Test2().method2();
    }
    public void method1(){
        int i = 1; 
        int a = i++;
        System.out.println(a); //打印1
    }

     public void method2(){
        int i = 1;
        int a = ++i;
        System.out.println(a);//打印2
    }
}

2.5.1 、查看class字节码

Classfile /E:/cn/zjq/jvm/Test2.class
  Last modified 2021-7-28; size 572 bytes
  MD5 checksum 3a7509b80d2f5064b662c1912a716e1b
  Compiled from "Test2.java"
public class Test2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // Test2
   #3 = Methodref          #2.#19         // Test2."<init>":()V
   #4 = Methodref          #2.#21         // Test2.method1:()V
   #5 = Methodref          #2.#22         // Test2.method2:()V
   #6 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;
   #7 = Methodref          #25.#26        // java/io/PrintStream.println:(I)V
   #8 = Class              #27            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               method1
  #16 = Utf8               method2
  #17 = Utf8               SourceFile
  #18 = Utf8               Test2.java
  #19 = NameAndType        #9:#10         // "<init>":()V
  #20 = Utf8               Test2
  #21 = NameAndType        #15:#10        // method1:()V
  #22 = NameAndType        #16:#10        // method2:()V
  #23 = Class              #28            // java/lang/System
  #24 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #25 = Class              #31            // java/io/PrintStream
  #26 = NameAndType        #32:#33        // println:(I)V
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (I)V
{
  public Test2();
    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 1: 0

  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: new           #2                  // class Test2
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: invokevirtual #4                  // Method method1:()V
        10: new           #2                  // class Test2
        13: dup
        14: invokespecial #3                  // Method "<init>":()V
        17: invokevirtual #5                  // Method method2:()V
        20: return
      LineNumberTable:
        line 3: 0
        line 4: 10
        line 5: 20

  public void method1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_1
         1: istore_1
         2: iload_1
         3: iinc          1, 1
         6: istore_2
         7: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_2
        11: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
        14: return
      LineNumberTable:
        line 7: 0
        line 8: 2
        line 9: 7
        line 10: 14

  public void method2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_1
         1: istore_1
         2: iinc          1, 1
         5: iload_1
         6: istore_2
         7: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_2
        11: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
        14: return
      LineNumberTable:
        line 13: 0
        line 14: 2
        line 15: 7
        line 16: 14
}
SourceFile: "Test2.java"

对比

i++:

0: iconst_1 //将数字1压入到操作栈
1: istore_1 //将数字1从操作栈弹出,压入到本地变量表中,下标为1
2: iload_1 //从本地变量表中获取下标为1的数据,压入到操作栈中
3: iinc 1, 1 // 将本地变量中的1,再+1
6: istore_2 // 将数字1从操作栈弹出,压入到本地变量表中,下标为2
7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_2 //从本地变量表中获取下标为2的数据,压入到操作栈中
11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
14: return

++i:

0: iconst_1 //将数字1压入到操作栈
1: istore_1 //将数字1从操作栈弹出,压入到本地变量表中,下标为1
2: iinc 1, 1// 将本地变量中的1,再+1
5: iload_1 //从本地变量表中获取下标为1的数据(2),压入到操作栈中
6: istore_2 //将数字2从操作栈弹出,压入到本地变量表中,下标为2
7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_2 //从本地变量表中获取下标为2的数据(2),压入到操作栈中
11: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
14: return

区别:

  • i++

    • 只是在本地变量中对数字做了相加,并没有将数据压入到操作栈
    • 将前面拿到的数字1,再次从操作栈中拿到,压入到本地变量中
  • ++i

    • 将本地变量中的数字做了相加,并且将数据压入到操作栈
    • 将操作栈中的数据,再次压入到本地变量中

小结:可以通过查看字节码的方式对代码的底层做研究,探究其原理。

字符串拼接

字符串的拼接在开发过程中使用是非常频繁的,常用的方式有三种:

  • +号拼接:str+"456"
  • StringBuilder拼接
  • StringBuffer拼接

StringBuffer是保证线程安全的,效率是比较低的,我们更多的是使用场景是不会涉及到线程安全的问题的,所以更多的时候会选择StringBuilder,效率会高一些。
那么,问题来了,StringBuilder和“+”号拼接,哪个效率高呢?接下来我们通过字节码的 方式进行探究。
首先,编写个示例:

package cn.zjq.jvm;

public class Test3 {

    public static void main(String[] args) {
        new Test3().m1();
        new Test3().m2();
    }

    public void m1(){
        String s1 = "123";
        String s2 = "456";
        String s3 = s1 + s2;
        System.out.println(s3);
    }

    public void m2(){
        String s1 = "123";
        String s2 = "456";
        StringBuilder sb = new StringBuilder();
        sb.append(s1);
        sb.append(s2);
        String s3 = sb.toString();
        System.out.println(s3);
    }
}

查看Test3.class的字节码

Classfile /E:/jvm-test/src/main/java/cn/zjq/jvm/Test3.class
  Last modified 2021-7-28; size 817 bytes
  MD5 checksum 51561188551689cee4e66aff5f7eb966
  Compiled from "Test3.java"
public class cn.zjq.jvm.Test3
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#25        // java/lang/Object."<init>":()V
   #2 = Class              #26            // cn/zjq/jvm/Test3
   #3 = Methodref          #2.#25         // cn/zjq/jvm/Test3."<init>":()V
   #4 = Methodref          #2.#27         // cn/zjq/jvm/Test3.m1:()V
   #5 = Methodref          #2.#28         // cn/zjq/jvm/Test3.m2:()V
   #6 = String             #29            // 123
   #7 = String             #30            // 456
   #8 = Class              #31            // java/lang/StringBuilder
   #9 = Methodref          #8.#25         // java/lang/StringBuilder."<init>":()V
  #10 = Methodref          #8.#32         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #11 = Methodref          #8.#33         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #12 = Fieldref           #34.#35        // java/lang/System.out:Ljava/io/PrintStream;
  #13 = Methodref          #36.#37        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #14 = Class              #38            // java/lang/Object
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               m1
  #22 = Utf8               m2
  #23 = Utf8               SourceFile
  #24 = Utf8               Test3.java
  #25 = NameAndType        #15:#16        // "<init>":()V
  #26 = Utf8               cn/zjq/jvm/Test3
  #27 = NameAndType        #21:#16        // m1:()V
  #28 = NameAndType        #22:#16        // m2:()V
  #29 = Utf8               123
  #30 = Utf8               456
  #31 = Utf8               java/lang/StringBuilder
  #32 = NameAndType        #39:#40        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = NameAndType        #41:#42        // toString:()Ljava/lang/String;
  #34 = Class              #43            // java/lang/System
  #35 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
  #36 = Class              #46            // java/io/PrintStream
  #37 = NameAndType        #47:#48        // println:(Ljava/lang/String;)V
  #38 = Utf8               java/lang/Object
  #39 = Utf8               append
  #40 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #41 = Utf8               toString
  #42 = Utf8               ()Ljava/lang/String;
  #43 = Utf8               java/lang/System
  #44 = Utf8               out
  #45 = Utf8               Ljava/io/PrintStream;
  #46 = Utf8               java/io/PrintStream
  #47 = Utf8               println
  #48 = Utf8               (Ljava/lang/String;)V
{
  public cn.zjq.jvm.Test3();
    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

  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: new           #2                  // class cn/zjq/jvm/Test3
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: invokevirtual #4                  // Method m1:()V
        10: new           #2                  // class cn/zjq/jvm/Test3
        13: dup
        14: invokespecial #3                  // Method "<init>":()V
        17: invokevirtual #5                  // Method m2:()V
        20: return
      LineNumberTable:
        line 6: 0
        line 7: 10
        line 8: 20

  public void m1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: ldc           #6                  // String 123
         2: astore_1
         3: ldc           #7                  // String 456
         5: astore_2
         6: new           #8                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
        13: aload_1
        14: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        17: aload_2
        18: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: astore_3
        25: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        28: aload_3
        29: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        32: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 6
        line 14: 25
        line 15: 32

  public void m2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=1
         0: ldc           #6                  // String 123
         2: astore_1
         3: ldc           #7                  // String 456
         5: astore_2
         6: new           #8                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
        13: astore_3
        14: aload_3
        15: aload_1
        16: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: pop
        20: aload_3
        21: aload_2
        22: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        25: pop
        26: aload_3
        27: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        30: astore        4
        32: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        35: aload         4
        37: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        40: return
      LineNumberTable:
        line 18: 0
        line 19: 3
        line 20: 6
        line 21: 14
        line 22: 20
        line 23: 26
        line 24: 32
        line 25: 40
}
SourceFile: "Test3.java"

从解字节码中可以看出,m1()方法源码中是使用+号拼接,但是在字节码中也被编译成了StringBuilder方式。所以,可以得出结论,字符串拼接,+号和StringBuilder是相等的,效率一样。 接下来,我们再看一个案例:

package cn.zjq.jvm;

public class Test4 {

    public static void main(String[] args) {
        new Test4().m1();
        new Test4().m2();
    }

    public void m1(){
        String str = "";
        for (int i = 0; i < 5; i++) {
            str = str + i;
        }
        System.out.println(str);
    }

    public void m2(){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 5; i++) {
            sb.append(i);
        }
        System.out.println(sb.toString());
    }
}

m1() 与m2() 哪个方法的效率高?依然是通过字节码的方式进行探究。

Classfile /E:jvm/jvm-test/src/main/java/cn/zjq/jvm/Test4.class
  Last modified 2021-7-28; size 926 bytes
  MD5 checksum 7e80afebcbdc278af26a12995acfda40
  Compiled from "Test4.java"
public class cn.zjq.jvm.Test4
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#28        // java/lang/Object."<init>":()V
   #2 = Class              #29            // cn/zjq/jvm/Test4
   #3 = Methodref          #2.#28         // cn/zjq/jvm/Test4."<init>":()V
   #4 = Methodref          #2.#30         // cn/zjq/jvm/Test4.m1:()V
   #5 = Methodref          #2.#31         // cn/zjq/jvm/Test4.m2:()V
   #6 = String             #32            //
   #7 = Class              #33            // java/lang/StringBuilder
   #8 = Methodref          #7.#28         // java/lang/StringBuilder."<init>":()V
   #9 = Methodref          #7.#34         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #10 = Methodref          #7.#35         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #11 = Methodref          #7.#36         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #12 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
  #13 = Methodref          #39.#40        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #14 = Class              #41            // java/lang/Object
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               m1
  #22 = Utf8               StackMapTable
  #23 = Class              #42            // java/lang/String
  #24 = Utf8               m2
  #25 = Class              #33            // java/lang/StringBuilder
  #26 = Utf8               SourceFile
  #27 = Utf8               Test4.java
  #28 = NameAndType        #15:#16        // "<init>":()V
  #29 = Utf8               cn/zjq/jvm/Test4
  #30 = NameAndType        #21:#16        // m1:()V
  #31 = NameAndType        #24:#16        // m2:()V
  #32 = Utf8
  #33 = Utf8               java/lang/StringBuilder
  #34 = NameAndType        #43:#44        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #35 = NameAndType        #43:#45        // append:(I)Ljava/lang/StringBuilder;
  #36 = NameAndType        #46:#47        // toString:()Ljava/lang/String;
  #37 = Class              #48            // java/lang/System
  #38 = NameAndType        #49:#50        // out:Ljava/io/PrintStream;
  #39 = Class              #51            // java/io/PrintStream
  #40 = NameAndType        #52:#53        // println:(Ljava/lang/String;)V
  #41 = Utf8               java/lang/Object
  #42 = Utf8               java/lang/String
  #43 = Utf8               append
  #44 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #45 = Utf8               (I)Ljava/lang/StringBuilder;
  #46 = Utf8               toString
  #47 = Utf8               ()Ljava/lang/String;
  #48 = Utf8               java/lang/System
  #49 = Utf8               out
  #50 = Utf8               Ljava/io/PrintStream;
  #51 = Utf8               java/io/PrintStream
  #52 = Utf8               println
  #53 = Utf8               (Ljava/lang/String;)V
{
  public cn.zjq.jvm.Test4();
    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

  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: new           #2                  // class cn/zjq/jvm/Test4
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: invokevirtual #4                  // Method m1:()V
        10: new           #2                  // class cn/zjq/jvm/Test4
        13: dup
        14: invokespecial #3                  // Method "<init>":()V
        17: invokevirtual #5                  // Method m2:()V
        20: return
      LineNumberTable:
        line 6: 0
        line 7: 10
        line 8: 20

  public void m1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #6                  // String
         2: astore_1    // 将空字符串压入到本地变量表中的下标为1的位置
         3: iconst_0    // 将数字0压入操作栈顶
         4: istore_2    // 将栈顶数字0压入到本地变量表中的下标为2的位置
         5: iload_2      // 将本地变量中下标为2的数字0压入操作栈顶
         6: iconst_5  // 将数字5压入操作栈顶
         7: if_icmpge    35    //比较栈顶两int型数值大小,当结果大于等于0时跳转到35
        10: new           #7                  // class java/lang/StringBuilder
        13: dup       //复制栈顶数值并将复制值压入栈顶(数字5)
        14: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
        17: aload_1
        18: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: iload_2     //将本地变量中下标为2的数字0压入操作栈顶
        22: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        25: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        28: astore_1
        29: iinc          2, 1
        32: goto          5
        35: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        38: aload_1
        39: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        42: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 10
        line 12: 29
        line 15: 35
        line 16: 42
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 5
          locals = [ class java/lang/String, int ]
        frame_type = 250 /* chop */
          offset_delta = 29

  public void m2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #7                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
         7: astore_1
         8: iconst_0
         9: istore_2
        10: iload_2
        11: iconst_5
        12: if_icmpge     27
        15: aload_1
        16: iload_2
        17: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        20: pop
        21: iinc          2, 1
        24: goto          10
        27: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        30: aload_1
        31: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        37: return
      LineNumberTable:
        line 19: 0
        line 20: 8
        line 21: 15
        line 20: 21
        line 23: 27
        line 24: 37
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 10
          locals = [ class java/lang/StringBuilder, int ]
        frame_type = 250 /* chop */
          offset_delta = 16
}
SourceFile: "Test4.java"

可以看到,m1()方法中的循环体内,每一次循环都会创建StringBuilder对象,效率低于m2()方法。

小结

使用字节码的方式可以很好查看代码底层的执行,从而可以看出哪些实现效率高,哪些实现效率低。可以更好的对我们的代码做优化。让程序执行效率更高。

相关文章
|
18天前
|
XML JavaScript 前端开发
xml文件使用及解析
xml文件使用及解析
|
1月前
|
算法 Linux C++
【Linux系统编程】解析获取和设置文件信息与权限的Linux系统调用
【Linux系统编程】解析获取和设置文件信息与权限的Linux系统调用
29 0
|
22小时前
|
Linux Go 数据安全/隐私保护
Linux 中的文件属性解析
在 Linux 系统中,每个文件和目录有一组属性控制其操作和访问权限。了解这些属性对有效管理文件至关重要。文件属性包括:文件类型(如 `-` 表示普通文件,`d` 表示目录),权限(如 `rwx` 表示所有者权限,`r-x` 表示组和其他用户权限),所有者,组,硬链接数,文件大小和最后修改时间。通过 `chown` 和 `chmod` 命令可更改文件所有者、所属组及权限。此外,还有特殊权限(如 SUID、SGID)和 ACL(访问控制列表)提供更精细的访问控制。
|
8天前
|
XML C# 数据格式
C# 解析XML文件
C# 解析XML文件
17 1
|
11天前
|
存储 XML 监控
JVM工作原理与实战(三):字节码文件的组成
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了字节码文件的基础信息、常量池、方法、字段、属性等内容。
26 6
|
19天前
|
存储 缓存 算法
深度解析JVM世界:垃圾判断和垃圾回收算法
深度解析JVM世界:垃圾判断和垃圾回收算法
|
2天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
10天前
yolo-world 源码解析(六)(2)
yolo-world 源码解析(六)
20 0
|
10天前
yolo-world 源码解析(六)(1)
yolo-world 源码解析(六)
13 0
|
10天前
yolo-world 源码解析(五)(4)
yolo-world 源码解析(五)
22 0

推荐镜像

更多