【JVM】字节码指令

1. 方法的执行流程

原始Java代码

public class Demo3_1 {
    
    
    public static void main(String[] args) {
    
    
        int a = 10;
        int b = Short.MAX_VALUE + 1;
        int c = a + b;
        System.out.println(c);
    }
}

编译后的字节码文件

[root@localhost ~]# javap -v Demo3_1.class
Classfile /root/Demo3_1.class
Last modified Jul 7, 2019; size 665 bytes
MD5 checksum a2c29a22421e218d4924d31e6990cfc5
Compiled from "Demo3_1.java"
public class cn.itcast.jvm.t3.bytecode.Demo3_1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#26 // java/lang/Object."<init>":()V
#2 = Class #27 // java/lang/Short
#3 = Integer 32768
#4 = Fieldref #28.#29 //
java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #30.#31 // java/io/PrintStream.println:(I)V
#6 = Class #32 // cn/itcast/jvm/t3/bytecode/Demo3_1
#7 = Class #33 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcn/itcast/jvm/t3/bytecode/Demo3_1;
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 a
#20 = Utf8 I
#21 = Utf8 b
#22 = Utf8 c
#23 = Utf8 MethodParameters
#24 = Utf8 SourceFile
#25 = Utf8 Demo3_1.java
#26 = NameAndType #8:#9 // "<init>":()V
#27 = Utf8 java/lang/Short
#28 = Class #34 // java/lang/System
#29 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#30 = Class #37 // java/io/PrintStream
#31 = NameAndType #38:#39 // println:(I)V
#32 = Utf8 cn/itcast/jvm/t3/bytecode/Demo3_1
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (I)V
{
public cn.itcast.jvm.t3.bytecode.Demo3_1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/itcast/jvm/t3/bytecode/Demo3_1;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: ldc #3 // int 32768
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: getstatic #4 // Field
java/lang/System.out:Ljava/io/PrintStream;
13: iload_3
14: invokevirtual #5 // Method
java/io/PrintStream.println:(I)V
17: return
LineNumberTable:
line 8: 0
line 9: 3
3)常量池载入运行时常量池
4)方法字节码载入方法区
5)main 线程开始运行,分配栈帧内存
(stack=2,locals=4)
line 10: 6
line 11: 10
line 12: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 args [Ljava/lang/String;
3 15 1 a I
6 12 2 b I
10 8 3 c I
MethodParameters:
Name Flags
args
}

1.1 常量池载入运行时常量池

首先,会由JVM的类加载器把main方法所在的这个类进行一个类加载的操作。

也就是字节码文件读取到内存中来。其中常量池这部分的数据,会放在内存的运行时常量池中。

不知道什么是运行时常量池的同学可以看看这个【JavaSE】浅析String与StringTable_起名方面没有灵感的博客-CSDN博客

image-20230306223426646

一些比较小的数字,比如10这样的数字,并不存储在运行时常量池的,它是跟着方法的字节码指令存储在一起的。

一旦这个数字超过了整数的最大值(Short.MAX_VALUE),那么就会存储在常量池中。


1.2 方法字节码载入方法区

方法的一些字节码,则会放在方法区

image-20230306224057252


1.3 main线程开始运行,分配栈帧内存

在栈帧中有两个东西,分别是局部变量表和操作数栈。

在字节码文件中已经规定了这两个东西的大小了。

Code:
stack=2, locals=4, args_size=1

也就是操作数栈为2,而局部变量表为4。

image-20230306224233281


1.4 执行引擎开始执行字节码

根据方法区的字节码文件,执行流程如下。

执行bipush 10,这个指令的意思是将一个byte压进操作数栈(其长度会补齐4字节),类似的指令还有

  • sipush将一个short压进操作数栈(其长度会补齐4个字节)
  • ldc将一个int压入操作数栈
  • ldc_w将一个long压入操作数栈(分两次压进,因为long是8字节)
  • 而小的数字都是和字节码指令存在一起的,超过short范围的数字存储在常量池

image-20230307104059705

接着,执行istore_1,这个之林的意思就是存入局部变量表slot 1

image-20230307104317435

ldc #3,这个指令是从常量池#3数据加载到操作数栈中

image-20230307104435272

istore2,接着将操作数栈中的32768放置在局部变量表的2号槽位。

image-20230307104554000

iload1 与 iload2指令是将局部变量表的1号与2号位置的数据依次放置进操作数中

image-20230307104711304

image-20230307104719638

isadd,这个指令对应的就是加法运算,执行引擎会去执行add,操作数栈会弹出两个变量,执行引擎完成add操作后,会再次压进操作数栈

image-20230307105020947

istore 3接着将操作数栈中的变量弹出,放置在3号位置中,也就是赋值给c

image-20230307105107244

getstatic #4,到常量池中查找一个静态对象,加载进操作数中,接着load 3将3号槽位的数据压进操作数,invokevirtual #5指令就是找到常量池#5项,定位到方法去java/io/PrintStream.println(I)方法,生成新的栈帧,传递参数并执行新栈帧的字节码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9AX35Ax-1678159317918)(https://lnnu-tuchuang.oss-cn-guangzhou.aliyuncs.com/img/image-20230307105849420.png)]

执行完毕后,弹出栈帧,并且清除main操作数栈的内容。

image-20230307105927217

最后return,完成main方法的调用,弹出main栈帧,程序结束。


2. 条件判断

条件判断的指令如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCr9T3UI-1678159317919)(https://lnnu-tuchuang.oss-cn-guangzhou.aliyuncs.com/img/image-20230307110332183.png)]

需要注意的是,byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节


2.1 源码分析

public class Demo3_3 {
    
    
    public static void main(String[] args) {
    
    
        int a = 0;
        if(a == 0) {
    
    
            a = 10;
        } else {
    
    
            a = 20;
        }
    }
}
0: iconst_0
1: istore_1
2: iload_1
3: ifne 12
6: bipush 10
8: istore_1
9: goto 15
12: bipush 20
14: istore_1
15: return
  1. iconst_0,得到一个0的常量,压进操作数栈中
  2. istore_1,将这个操作数栈的这个元素放到局部变量表的1号位置,也就是给a赋值为0
  3. iload_1,再将a的值压进操作数栈
  4. ifne 12,判断是否 == 0
    1. 如果 != 0,那么就跳到12行字节码指令bipush 20中区,也就是向操作数栈中压入20,istore_1,将a赋值为20.
    2. 如果 == 0,那就是继续执行bipush 10istore_1,将a赋值为10,并且执行goto 15的时候,跳到第15行return

3. 循环控制指令

循环控制指令其实和条件判断差不多,只是利用了goto进行了循环


3.1 源码分析

public class Demo3_4 {
    
    
    public static void main(String[] args) {
    
    
        int a = 0;
        while (a < 10) {
    
    
            a++;
        }
    }
}
0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 14
8: iinc 1, 1
11: goto 2
14: return
  1. iconst_0istore_1,将a赋值为0。
  2. iload_1将a的值压进操作数栈
  3. bipush 10,将10压进操作数栈
  4. if_icmpge 14,判断两个int是否>=,如果不成立,则跳到14行指令去
  5. 如果成立,那么iinc 1, 1,局部变量表的1号位置,自增1,接着goto 2,继续跳回2号位置。

参考:黑马程序员JVM完整教程,Java虚拟机快速入门,全程干货不拖沓)

猜你喜欢

转载自blog.csdn.net/weixin_51146329/article/details/129378957