字节码层面剖析程序执行过程
前言
利用jclasslib插件查看源代码生成的字节码,剖析程序代码在虚拟机内部的运行方式。
相关知识点
- 虚拟机栈是线程私有
- 虚拟机栈由栈帧组成
- 栈帧由局部变量表,操作数栈,动态链接,返回地址,附加信息组成。
- 局部变量表:大小编译后已确定,由槽组成,保存局部变量
- 操作数栈:相关操作需要入栈出栈完成操作
- 动态链接:每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,如子类重写方法都是动态链接到方法区自己的字节码指令,而不是父类的。
- 返回地址: 存放调用该方法的pc寄存器的值,即是上个方法运行位置。
- 附加信息:对程序调试提供支持的信息等
- 程序计数器线程私有,保存下个指令运行位置。
- 字节码指令查看网址
案例剖析
public class MethodAreaDemo {
public static void main(String[] args) {
int x = 500;
int y = 100;
int a = x / y;
int b = 50;
System.out.println(a + b);
}
}
编译后字节码:
0 sipush 500 //把500入栈操作数栈
3 istore_1 //出栈保存到局部变量表槽1
4 bipush 100 //把100入栈操作数栈
6 istore_2 //出栈保存到局部变量表槽2
7 iload_1 //读取局部变量表槽1入栈到操作数栈
8 iload_2 //读取局部变量表槽2入栈到操作数栈
9 idiv //出栈两个型,相除结果入栈
10 istore_3 //出栈保存到局部变量表槽3
11 bipush 50 //把50入栈操作数栈
13 istore 4 //出栈保存到局部变量表槽4
15 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> //类或接口的值字段获取并推到操作数堆栈。获取#2
18 iload_3 //读取局部变量表槽3入栈到操作数栈
19 iload 4 //读取局部变量表槽4入栈到操作数栈
21 iadd //出栈两个,相加结果入栈
22 invokevirtual #3 <java/io/PrintStream.println : (I)V> //调用静态方法,新的栈帧,操作数栈出栈为新栈帧的入参参数
25 return //栈帧结果返回
-
main方法开始执行,args入参保存在局部变量表槽0
2.
0 sipush 500:把500入栈操作数栈 -
3 istore_1:出栈500保存到局部变量表槽1 -
4 bipush 100:把100入栈操作数栈 -
6 istore_2:出栈100保存到局部变量表槽26.
7 iload_1:读取局部变量表槽1(500)入栈到操作数栈 -
8 iload_2:读取局部变量表槽2(100)入栈到操作数栈8.
9 idiv:出栈两个(100,500),相除结果(5)入栈 -
10 istore_3:出栈保存到局部变量表槽3 -
11 bipush 50:把50入栈操作数栈11.
13 istore 4:出栈50保存到局部变量表槽4 -
15 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>:类或接口的值字段获取并推到操作数堆栈。获取#2符号引用对应的静态变量 -
18 iload_3:读取局部变量表槽3(5)入栈到操作数栈 -
19 iload 4:读取局部变量表槽4(50)入栈到操作数栈 -
21 iadd:出栈两个(50,5),相加结果入栈(55) -
22 invokevirtual #3 <java/io/PrintStream.println : (I)V>:调用静态方法,新的栈帧,操作数栈出栈为新栈帧的入参参数,当前序号保存到程序计数器,新栈帧的方法返回地址也保存了当前程序运行的序号,当新栈帧运行结束(打印),返回当前位置。17.
25 return:栈帧结束返回
++i,i++和i=i++区别
public class test01 {
public static void main(String[] args) {
int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
System.out.println(i);
System.out.println(j);
System.out.println(k);
}
}
输出结果:
4
1
11
编译的字节码指令
0 iconst_1 //把常量1入栈到操作数栈
1 istore_1 //出栈(1)保存到局部变量表槽1
2 iload_1 //读取局部变量表槽1(1)入栈到操作数栈
3 iinc 1 by 1 //将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为2)。第一个位置1代表索引,第二个1表示常量。
6 istore_1 //出栈(1)保存到局部变量表槽1(2变为1)
7 iload_1 //读取局部变量表槽1(1)入栈到操作数栈
8 iinc 1 by 1 //将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为2)。
11 istore_2 //出栈(1)保存到局部变量表槽2
12 iload_1 //读取局部变量表槽1(2)入栈到操作数栈
13 iinc 1 by 1 //将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为3)。
16 iload_1 //读取局部变量表槽1(3)入栈到操作数栈
17 iload_1 //读取局部变量表槽1(3)入栈到操作数栈
18 iinc 1 by 1 //将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为4)。
21 imul //操作数栈出栈两个数(3,3),相乘结果(9)入栈
22 iadd //操作数栈出栈两个数(9,2),相加结果(11)入栈
23 istore_3 //出栈(11)保存到局部变量表槽3(11)
24 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> //获取相关类
27 iload_1 //读取局部变量表槽1(4)入栈到操作数栈
28 invokevirtual #3 <java/io/PrintStream.println : (I)V> //调用方法
31 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> //获取相关类
34 iload_2 //读取局部变量表槽2(1)入栈到操作数栈
35 invokevirtual #3 <java/io/PrintStream.println : (I)V> //调用方法打印
38 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> //获取相关类
41 iload_3 //读取局部变量表槽3(11)入栈到操作数栈
42 invokevirtual #3 <java/io/PrintStream.println : (I)V> //调用方法打印
45 return //栈帧结束返回
字节码剖析
-
开始
-
0 iconst_1:把常量1入栈到操作数栈,程序计数器指向下个指令
1 istore_1:出栈(1)保存到局部变量表槽1
2 iload_1:读取局部变量表槽1(1)入栈到操作数栈
3 iinc 1 by 1:将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为2)。第一个位置1代表索引,第二个1表示常量。
注意:直接在局部变量表自增1
6 istore_1:出栈(1)保存到局部变量表槽1(覆盖原来2:由2变为1)
6.7 iload_1 :读取局部变量表槽1(1)入栈到操作数栈
8 iinc 1 by 1:将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为2)。
11 istore_2:出栈(1)保存到局部变量表槽2
12 iload_1:读取局部变量表槽1(2)入栈到操作数栈
13 iinc 1 by 1:将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为3)。
11.16 iload_1 :读取局部变量表槽1(3)入栈到操作数栈
-
17 iload_1:读取局部变量表槽1(3)入栈到操作数栈 -
18 iinc 1 by 1:将局部变量表中的槽1的变量按常量1增加(此时局部变量表槽1的数为4)。
21 imul:操作数栈出栈两个数(3,3),相乘结果(9)入栈
22 iadd:操作数栈出栈两个数(9,2),相加结果(11)入栈
23 istore_3:出栈(11)保存到局部变量表槽3(11)
- 创建新栈帧,读局部变量表数作为下个栈帧的入参,入参进入操作数栈,出栈并打印
24 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> :获取相关类
27 iload_1 :读取局部变量表槽1(4)入栈到操作数栈
28 invokevirtual #3 <java/io/PrintStream.println : (I)V> :调用方法打印
- 步骤同上
31 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> :获取相关类
34 iload_2 :读取局部变量表槽2()入栈到操作数栈
35 invokevirtual #3 <java/io/PrintStream.println : (I)V> :调用方法打印
- 步骤同上
38 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> :获取相关类
41 iload_3 :读取局部变量表槽2()入栈到操作数栈
42 invokevirtual #3 <java/io/PrintStream.println : (I)V> :调用方法打印
45 return:栈帧结束返回
宏观分析
int i = 1;
先入栈1,出栈到槽1:此时:局部变量表i=1,操作数栈空
i = i++;
先读取局部变量变i=1入栈,局部变量表i自增为2,此时:局部变量表i=2,操作数栈i=1
又赋值操作 i=操作数栈值,出栈i=1赋值给i,此时:局部变量表i=1,操作数栈空(操作数栈i覆盖了局部变量表i)
int j = i++;
先读取局部变量变i=1入栈,局部变量表i自增为2,此时:局部变量表i=2,操作数栈i=1
又赋值操作 int j=操作数栈值,出栈i=1赋值给j,此时:局部变量表i=2,j=1,操作数栈空
int k = i + ++i * i++;
i:读取局部变量变i=2入栈。此时:局部变量表i=2,j=1,操作数栈2
++i: 局部变量表i自增为3,读取局部变量表i=3入栈。此时:局部变量表i=3,j=1,操作数栈2,3
i++:读取局部变量变i=3入栈,局部变量表i=3自增为4.此时:局部变量表i=4,j=1,操作数栈2,3,3
int k =操作数栈值:k=2+3*3=11.此时:局部变量表i=4,j=1,k=11;操作数栈空
总结
++i: 先局部变量表的i自增,再读取局部变量表的i入栈。
i++:先读取局部变量表的i入栈,再局部变量表的i自增。
i = i++:先读取局部变量表的i入栈,再局部变量表的i自增,最后把操作数栈的值数赋值给局部变量表的i
深入理解JVM系列
- 1.深入理解JVM(一)一一 简介和体系结构
- 2.深入理解JVM(二)一一 类加载器子系统
- 3.深入理解JVM(三)一一 运行时数据区(虚拟机栈)
- 4.深入理解JVM(四)一一 运行时数据区(程序计数器+本地方法栈)
- 5.深入理解JVM(五)一一 运行时数据区(堆)
- 6.深入理解JVM(六)一一 运行时数据区(方法区)
- 7.深入理解JVM(七)一一 执行引擎(解释器和JIT编译器)
- 8.深入理解JVM(八)一一 字符串常量池
- 9.深入理解JVM(九)一一 对象实例化和内存布局
- 10.深入理解JVM(十)一一 字节码层面剖析程序执行过程
- 11.深入理解JVM(十一)一一 垃圾回收相关概念
- 12.深入理解JVM(十二)一一 垃圾回收相关算法
- 13.深入理解JVM(十三)一一 详解垃圾回收器
- 14.深入理解JVM(十四)一一 对象分布图
- 15.深入理解JVM(十五)一一 class文件结构
- 16.深入理解JVM(十六)一一 字节码指令集
- 17.深入理解JVM(十七)一一 类的生命周期详解
- 18.深入理解JVM(十八)一一 再谈类的加载器
- 19.深入理解JVM(十九)一一 JVM监控及诊断工具(命令行)
- 20.深入理解JVM(二十)一一 JVM监控及诊断工具(GUI)
- 21.深入理解JVM(二十一)一一 JVM运行时参数(收藏篇)
- 22.深入理解JVM(二十二)一一 分析GC日志
- 23.深入理解JVM(二十三)一一 OOM场景及解决方案