JVM 字节码执行实例分析
前言
最近在看《Java 虚拟机规范》和《深入理解JVM虚拟机》,对于字节码的执行有了进一步的了解。字节码就像是汇编语言,是 JVM 的指令集。下面我们先对 JVM 执行引擎做一下简单介绍,然后根据实例分析 JVM 字节码的执行过程。包括:
- for 循环字节码分析
- try-catch-finally 字节码分析
运行时栈帧结构
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
在编译程序员代码的时候,栈帧中局部变量表和操作数栈的大小已经确定了,并且写入到方法表中的 Code 属性中。
在活动线程中,只有位于栈顶的栈帧才是有效的, 称为当前栈帧,与这个栈帧关联的方法称为当前方法。执行引擎运行的所有字节码指令只对当前栈帧进行操作。
局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表的容量以变量槽(slot)为最小单位,每个 slot 保证能放下 32 位内的数据类型。虚拟机通过索引定位的方式使用局部变量表,索引值从 0 开始。值得注意的是,对于实例方法,局部变量表中第 0 位索引的 slot 默认是 this引用;静态方法则不是。而且为了节约内存,slot 是可以重用的。
操作数栈
操作数栈的元素可以是任意的 Java 数据类型。当一个方法开始时,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈入栈操作。
实例分析
下面分析的字节码指令主要是对局部变量表和操作栈的读写。
for 循环字节码分析
void spin() {
int i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
上面是一个空循环的代码,编译后的字节码如下:
Method void spin()
0 iconst_0 // Push int constant 0
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don’t increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done
相信大家看到上面的代码都是一脸懵逼,即使有注释还是不知道字节码到底做了什么操作。下面我就图解每一条指令,帮助理解。上面的代码都是对局部变量表和操作数栈的操作,所以我们的关注点就在这两个区域上。(栈是自顶向下的)
0 iconst_0 //把常量0放入栈 +--------+--------+ | local | stack | +-----------------+ | | 0 | +-----------------+ | | | +--------+--------+ 1 istore_1 //把栈顶的元素出栈,存到局部变量表索引为1的位置 +--------+--------+ | local | stack | +-----------------+ | 0 | | +-----------------+ | | | +--------+--------+ 2 goto 8 //跳转到第8条指令 8 iload_1 //把局部变量表中索引为1的变量入栈 +--------+--------+ | local | stack | +-----------------+ | 0 | 0 | +-----------------+ | | | +--------+--------+ 9 bipush 100 //把100入栈 +--------+--------+ | local | stack | +-----------------+ | 0 | 0 | +-----------------+ | | 100 | +--------+--------+ 11 if_icmplt 5 //出栈两个元素v1,v2,比较它们的值,当且仅当v1 < v2,跳转到指令5 +--------+--------+ | local | stack | +-----------------+ | 0 | | +-----------------+ | | | +--------+--------+ 5 iinc 1 1 //自增局部变量表中索引为1的值 +--------+--------+ | local | stack | +-----------------+ | 1 | | +-----------------+ | | | +--------+--------+ //进行下次循环直到指令11不满足,到达指令14 14 return //清空栈,执行引擎把控制权交换给调用者。 +--------+--------+ | local | stack | +-----------------+ | 100 | | +-----------------+ | | | +--------+--------+
以上就是for循环字节码执行的过程。可以发现,所有指令都是围绕者局部变量表和操作数栈在操作。
解惑
指令iconst_0,iload_1的命名解读
第一个i代表这是对int数据类型进行的操作const,load是操作码0,1是隐含的操作数
上面的两个指令等价于iconst 0,iload 1
详细的字节码解释查阅《JVM 虚拟机规范》
try-catch-finally 字节码分析
static int inc(){
int x;
try {
x = 1;
return x;
} catch (Exception e){
x = 2;
return x;
} finally {
x = 3;
}
}
下面是它的字节码,这次我就不画图了,里面的命令跟上面的类似。
static int inc();
descriptor: ()I
flags: ACC_STATIC
Code:
stack=1, locals=4, args_size=0
0: iconst_1 //try 块中的 x = 1;
1: istore_0 //保存栈顶元素到局部变量表中索引为 0 的 slot 中
2: iload_0 //加载局部变量表中索引为 0 的值到栈中
3: istore_1 //保存栈顶元素到局部变量表中索引为 1 的 slot 中
4: iconst_3 //finally 块中的 x = 3;
5: istore_0 //保存栈顶元素到局部变量表中索引为 0 的 slot 中,x 的值存在这里。
6: iload_1 //加载局部变量表中索引为 1 的值到栈中
7: ireturn //返回栈顶元素,即 x = 1;正常情况下函数运行到这里就结束了,如果出现异常根据异常表跳转到指定的位置
8: astore_1 //给 catch 块中定义的 Exception e 赋值,存储在 slot1 中。
9: iconst_2 //catch 块中的 x = 2;
10: istore_0
11: iload_0
12: istore_2
13: iconst_3 //finally 块中的 x = 3;
14: istore_0
15: iload_2
16: ireturn //此时返回的是 slot2 中的值,即 x = 2
17: astore_3 //如果出现不属于 java.lang.Exception 及其子类的异常,才会根据异常表中的规则跳转到这里。
18: iconst_3 //finally 块中的 x = 3;
19: istore_0
20: aload_3 //将异常加载到栈顶,
21: athrow //抛出栈顶的异常
Exception table:
from to target type
0 4 8 Class java/lang/Exception
0 4 17 any
8 13 17 any
字节码中 0 ~ 4 行将整数 1 赋值为变量 x,x 存储在 slot0 中,并且将 x 的值拷贝一份放到 slot1。如果没有出现异常,继续走到 5 ~ 7 行,将 x 赋值为 3,然后读取 slot1 中的值到栈顶,最后ireturn返回栈顶的值,方法结束。
如果出现异常,PC 寄存器指针转到第 8 行,第 8 ~ 16 行所做的事情就是将 2 赋值给 x,然后保存 x 的拷贝,最后将 x 赋值为 3。方法返回前将 x 的拷贝 2 读取到栈顶。
如果在 0 ~ 4,8 ~ 13 行中出现其他异常,则跳转到第 17 行执行,先同样执行finally块中的x = 3,最后抛出异常,方法结束。
可以看到,Java 的异常处理是通过异常表的方式来决定代码执行的路径。而finally的实现是通过在每个路径的最后加入finally块中的字节码实现的。
参考资料
《Java 虚拟机规范》、《深入理解JVM虚拟机》
JVM 字节码执行实例分析的更多相关文章
- JVM总结(五):JVM字节码执行引擎
JVM字节码执行引擎 运行时栈帧结构 局部变量表 操作数栈 动态连接 方法返回地址 附加信息 方法调用 解析 分派 –“重载”和“重写”的实现 静态分派 动态分派 单分派和多分派 JVM动态分派的实现 ...
- 一夜搞懂 | JVM 字节码执行引擎
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...
- 从JVM字节码执行看重载和重写
Java 重写(Override)与重载(Overload) 重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即外壳不变,核心重写! 重写的 ...
- 深入理解JVM—字节码执行引擎
原文地址:http://yhjhappy234.blog.163.com/blog/static/3163283220122204355694/ 前面我们不止一次的提到,Java是一种跨平台的语言,为 ...
- JVM字节码执行引擎和动态绑定原理
1.执行引擎 所有Java虚拟机的执行引擎都是一致的: 输入的是字节码文件,处理过程就是解析过程,最后输出执行结果. 在整个过程不同的数据在不同的结构中进行处理. 2.栈帧 jvm进行方法调用和方法执 ...
- jvm 字节码执行 (一)方法调用
“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上,而虚拟机的执行引擎是 由自己实现的,因此可以自行制定指令集 ...
- JVM字节码执行引擎
一.概述 在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译器执行(通过即时编译器产生本地代码执行)两种选择,所有的Java虚拟机的执行引擎都是一致的:输 ...
- jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行
动态类型语言 动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期. 举例子解释“类型检查”,例如代码: obj.println("hello world"); 假 ...
- 图解JVM字节码执行引擎之栈帧结构
一.执行引擎 “虚拟机”的概念是相对于“物理机”而言的,这两种“机器”都有执行代码的能力.物理机的执行引擎是直接建立在硬件处理器.物理寄存器.指令集和操作系统层面的:而“虚拟机”的执行引擎是 ...
随机推荐
- C++输入输出缓冲区的刷新问题
当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起.这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介.例如,对于一个输出 ...
- Java---Hibernate>>Can't create table './xxx/#sql-b2c_1a.frm' (errno: xxx)解决方法
通用方案:删除相关表,重新生成. 1.关联表之间数据引擎不一致导致: 修改相关表的引擎设定,保持一致. 2.关联表索引字段的引用类型不一样(如A表关联字段是int,B表索引是char): 修改相关表的 ...
- Nginx(一)初始环境的安装(php5.3+mysql5.1+fastcgi…)
关参考资源http://www.lnmp.org/index.html \\LNMP一键安装包http://www.howtocn.org/nginx \\Nginx模块参考手册中文版http://b ...
- 玩转HTML5移动页面(优化篇)
原文:http://www.grycheng.com/?p=472 承接上文<玩转HTML5移动页面(动效篇)>,上次说的是让页面动起来的一些小技巧.而页面动起来的根基是功能可用的页面,因 ...
- 【python】求水仙数
for i in range(100, 1000): sum = 0 temp = i while temp: sum = sum + (temp%10) ** 3 temp //= 10 # 注意使 ...
- Djang DJANGO_SETTINGS_MODULE
在 site-packages\django 新建一个文件 ’settings.py‘ 内容如下: DEBUG = TrueDEFAULT_FROM_EMAIL = 'alangwansui@qq.c ...
- 简单vim配置
对于一个学习C++的人来说,我常用VS写代码,而Linux环境也是很重要的.最近学习了Linux的一些基本内容后,开始了vim编辑器的使用.然而它刚开始的界面着实令人很不习惯~~~于是我尝试着把它装饰 ...
- IBM总架构师寇文东谈程序员的职业规划
有些年轻的程序员向我咨询,将来的路该怎么走?俗话说,条条大路通罗马.不同的路都能走向成功,到底选择哪条路,取决于自己的兴趣.可能有程序员会问:如果还没有找到自己的兴趣怎么办?我的建议是多尝试,努力做, ...
- STM32与S3C2440的区别
一.定位 STM32: 高功能单片机.工业控制 S3C2440: 处理器.智能设备 二.跑系统 STM32: ucos-II S3C2440: Linux等大型系统 三.硬件架构 STM32: C ...
- linq 动态排序
/// <summary> /// 排序 /// </summary> /// <typeparam name="T"></typepar ...