文章目录
- 1. 方法的执行流程
- 1.1 常量池载入运行时常量池
- 1.2 方法字节码载入方法区
- 1.3 main线程开始运行,分配栈帧内存
- 1.4 执行引擎开始执行字节码
- 2. 条件判断
- 2.1 源码分析
- 3. 循环控制指令
- 3.1 源码分析
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博客
一些比较小的数字,比如10这样的数字,并不存储在运行时常量池的,它是跟着方法的字节码指令存储在一起的。
一旦这个数字超过了整数的最大值(Short.MAX_VALUE
),那么就会存储在常量池中。
1.2 方法字节码载入方法区
方法的一些字节码,则会放在方法区
1.3 main线程开始运行,分配栈帧内存
在栈帧中有两个东西,分别是局部变量表和操作数栈。
在字节码文件中已经规定了这两个东西的大小了。
Code:
stack=2, locals=4, args_size=1
也就是操作数栈为2,而局部变量表为4。
1.4 执行引擎开始执行字节码
根据方法区的字节码文件,执行流程如下。
执行bipush 10
,这个指令的意思是将一个byte压进操作数栈(其长度会补齐4字节),类似的指令还有
sipush
将一个short
压进操作数栈(其长度会补齐4个字节)ldc
将一个int
压入操作数栈ldc_w
将一个long
压入操作数栈(分两次压进,因为long
是8字节)- 而小的数字都是和字节码指令存在一起的,超过
short
范围的数字存储在常量池
接着,执行istore_1
,这个之林的意思就是存入局部变量表slot 1
ldc #3
,这个指令是从常量池#3
数据加载到操作数栈中
istore2
,接着将操作数栈中的32768放置在局部变量表的2号槽位。
iload1 与 iload2
指令是将局部变量表的1号与2号位置的数据依次放置进操作数中
isadd
,这个指令对应的就是加法运算,执行引擎会去执行add
,操作数栈会弹出两个变量,执行引擎完成add
操作后,会再次压进操作数栈
istore 3
接着将操作数栈中的变量弹出,放置在3号位置中,也就是赋值给c
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操作数栈的内容。
最后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
iconst_0
,得到一个0的常量,压进操作数栈中istore_1
,将这个操作数栈的这个元素放到局部变量表的1号位置,也就是给a赋值为0iload_1
,再将a的值压进操作数栈ifne 12
,判断是否==
0- 如果
!=
0,那么就跳到12行字节码指令bipush 20
中区,也就是向操作数栈中压入20,istore_1
,将a赋值为20. - 如果
==
0,那就是继续执行bipush 10
和istore_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
iconst_0
与istore_1
,将a赋值为0。iload_1
将a的值压进操作数栈bipush 10
,将10压进操作数栈if_icmpge 14
,判断两个int是否>=
,如果不成立,则跳到14行指令去- 如果成立,那么
iinc 1, 1
,局部变量表的1号位置,自增1,接着goto 2
,继续跳回2号位置。
参考:黑马程序员JVM完整教程,Java虚拟机快速入门,全程干货不拖沓)