JVM字节码(class文件)解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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()方法。

小结

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

相关文章
|
17天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
66 6
|
2月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
31 3
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
49 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
2月前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
43 1
|
3月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
117 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
4月前
|
C# 开发者 Windows
震撼发布:全面解析WPF中的打印功能——从基础设置到高级定制,带你一步步实现直接打印文档的完整流程,让你的WPF应用程序瞬间升级,掌握这一技能,轻松应对各种打印需求,彻底告别打印难题!
【8月更文挑战第31天】打印功能在许多WPF应用中不可或缺,尤其在需要生成纸质文档时。WPF提供了强大的打印支持,通过`PrintDialog`等类简化了打印集成。本文将详细介绍如何在WPF应用中实现直接打印文档的功能,并通过具体示例代码展示其实现过程。
325 0
|
13天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
42 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
70 0
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0
|
2月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
62 0

推荐镜像

更多
下一篇
无影云桌面