前言

最近在看《Java 虚拟机规范》和《深入理解JVM虚拟机》,对于字节码的执行有了进一步的了解。字节码就像是汇编语言,是 JVM 的指令集。下面我们先对 JVM 执行引擎做一下简单介绍,然后根据实例分析 JVM 字节码的执行过程。包括:

  1. for 循环字节码分析
  2. 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 字节码执行实例分析的更多相关文章

  1. JVM总结(五):JVM字节码执行引擎

    JVM字节码执行引擎 运行时栈帧结构 局部变量表 操作数栈 动态连接 方法返回地址 附加信息 方法调用 解析 分派 –“重载”和“重写”的实现 静态分派 动态分派 单分派和多分派 JVM动态分派的实现 ...

  2. 一夜搞懂 | JVM 字节码执行引擎

    前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...

  3. 从JVM字节码执行看重载和重写

    Java 重写(Override)与重载(Overload) 重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即外壳不变,核心重写! 重写的 ...

  4. 深入理解JVM—字节码执行引擎

    原文地址:http://yhjhappy234.blog.163.com/blog/static/3163283220122204355694/ 前面我们不止一次的提到,Java是一种跨平台的语言,为 ...

  5. JVM字节码执行引擎和动态绑定原理

    1.执行引擎 所有Java虚拟机的执行引擎都是一致的: 输入的是字节码文件,处理过程就是解析过程,最后输出执行结果. 在整个过程不同的数据在不同的结构中进行处理. 2.栈帧 jvm进行方法调用和方法执 ...

  6. jvm 字节码执行 (一)方法调用

    “虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上,而虚拟机的执行引擎是 由自己实现的,因此可以自行制定指令集 ...

  7. JVM字节码执行引擎

    一.概述 在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译器执行(通过即时编译器产生本地代码执行)两种选择,所有的Java虚拟机的执行引擎都是一致的:输 ...

  8. jvm 字节码执行 (二)动态类型支持与基于栈的字节码解释执行

    动态类型语言 动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期. 举例子解释“类型检查”,例如代码: obj.println("hello world"); 假 ...

  9. 图解JVM字节码执行引擎之栈帧结构

    一.执行引擎      “虚拟机”的概念是相对于“物理机”而言的,这两种“机器”都有执行代码的能力.物理机的执行引擎是直接建立在硬件处理器.物理寄存器.指令集和操作系统层面的:而“虚拟机”的执行引擎是 ...

随机推荐

  1. left join, right join , inner join, join, union的意义

    数据库在连接两张或以上的表来返回数据时,都会生成一张中间的临时表,然后再将临时表返回给用户left join,right join,inner join, join 与 on 配合用 select c ...

  2. raywenderlich-iOS设计模式Part 1/2【译】

    原文地址:http://www.raywenderlich.com/86477/introducing-ios-design-patterns-in-swift-part-1 Update 04/22 ...

  3. source insight添加汇编文件.s

    (1) 做ARM嵌入式开发时,有时得整汇编代码,但在SIS里建立PROJECT并ADD TREE的时候,根据默认设置并不会把该TREE里面所有汇编文件都包含进来,默认只加了.inc和.asm后缀的, ...

  4. POJ 1699 Best Sequence dfs

    题目: http://poj.org/problem?id=1699 无意间A了..超时一次,加了一句 if(len > ans)return; 然后就A了,dfs题,没有太多好说的,代码写的效 ...

  5. xcode 必用插件二

    本文大致整理了自己用过的一些插件的使用感想(就是好不好用). 在那之前先简单贴两条插件须知,知道的可以忽略. 1.Alcatraz 类似于管理第三方库的cocoapods,管理插件也有个Alcatra ...

  6. AirPlay简介

    AirPlay是苹果公司在iOS4.2即Mac OS Mountain Lion中加入的一种播放技术.可以将iPhone.iPod.iPad和Mac上的视频镜像传送到支持AirPlay的设备上(如音响 ...

  7. NOI冲刺计划2

    吐槽:距离上一次写计划还没有一个月呢,咋又喊要重写捏?可以直接从上一次的计划粘上个一大半. bzoj刷题速度还是在计划之内的,这大半个月中,我bzoj刷进500道,知识方面主要是把莫比乌斯反演系统性的 ...

  8. OSI/RM网络7层体系

    转自OSI/RM网络7层体系 1 物理层 这是整个OSI参考模型的最低层,它的任务就是提供网络的物理连接.所以,物理层是建立在物理介质上(而不是逻辑上的协议和会话),它提供的是机械和电气接口.主要包括 ...

  9. poi 操作excel

    poi操作 创建一个excel关联对象HSSFWorkbook: HSSFWorkbook book = new HSSFWorkbook(); 创建一个sheet: HSSFSheet st = b ...

  10. 【Xamarin挖墙脚系列:如何从一个Apk程序转化为Xamarin的程序】

    原文:[Xamarin挖墙脚系列:如何从一个Apk程序转化为Xamarin的程序] 工欲善其事必先利其器:工具下载:http://pan.baidu.com/s/1skxjwgH 接下来,我用个小的应 ...