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字节码执行引擎之栈帧结构
一.执行引擎 “虚拟机”的概念是相对于“物理机”而言的,这两种“机器”都有执行代码的能力.物理机的执行引擎是直接建立在硬件处理器.物理寄存器.指令集和操作系统层面的:而“虚拟机”的执行引擎是 ...
随机推荐
- absolute之整体布局实现
要实现如图的布局,我最先想到是将header与footer绝对定位,但是发现在移动端会出现bug,经查资料发现用absolute实现整体布局非常好,还挺简单的. .header, .footer, . ...
- 此文件时入口文件index.php
此文件时入口文件index.php <?php //定义一下ThinkPHP框架存放的路径 define('THINK_PATH','./ThinkPHP/'); //定义当前的项目的名称,此处 ...
- node 无解回调 有解了
http://cssor.com/javascript-workflow-by-tofishes.html
- 【python】for循环一列
a="哈哈哈哈,笑死我了"for i in a: print (i, end=' ') 结果 哈 哈 哈 哈 , 笑 死 我 了
- myeclipse使用SVN团队开发
很多时候我们做项目都是已一个团队一起开发,所以我们可以选择SVN开发工具. 一:了解SVN的使用流程: 1).有一个服务端,他可以在上面添加一个项目,添加成员 2).myeclipse需要安装svn的 ...
- IntelliJ 一键添加双引号
IntelliJ IDEA(目前最新版本为2016.2.5) 不直接支持一键向选取内容添加双引号,但可以通过配置 Live Template 实现此功能. 以下为配置方法(针对2016.2.5版,其它 ...
- bzoj 3144: [Hnoi2013]切糕 最小割
3144: [Hnoi2013]切糕 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 681 Solved: 375[Submit][Status] ...
- USB究竟是什么?看完这篇文章我就明白了!
在早期,USB接口的出现似乎仅仅是要解决一些简单外设诸如鼠标.键盘之类的设备与电脑的连接问题,很显然,这些外设所产生的数据量是极低的,所以,USB规范所定义的数据速率是1.5Mbps,数据还是双向分时 ...
- 17.1.2 Replication Formats
17.1.2 Replication Formats 复制格式 17.1.2.1 Advantages and Disadvantages of Statement-Based and Row-Bas ...
- 【HDOJ】1072 Nightmare
bfs,使用ttl进行剪枝. #include <iostream> #include <cstdio> #include <cstring> #include & ...