一、概述

  在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译器执行(通过即时编译器产生本地代码执行)两种选择,所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。

  每个字节码指令都由一个1字节的操作码和附加的操作数组成。

二、运行时栈帧结构

  栈帧(Frame Frame)是用于支持虚拟机运行方法调用和执行的数据结构,每一个栈帧都包括了局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性中。

  重点理解:栈帧属于线程,每个线程中一般很多方法都同时处于执行状态。对于执行引擎来讲,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与之相关联的方法称为当前方法(即每一个方法都拥有自己独立的局部变量表、操作数栈、动态链接和方法的返回地址等信息)。

  

2.1 局部变量表

  局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。

  类变量有两次赋初始值的过程,一次是准备阶段,赋予系统初始值,整型 = 0,布尔类型 = false;另一次是初始化阶段,赋予程序员定义的初始值。因此即使在初始化阶段程序员没有为类变量赋值也没有关系,类变量仍然具有一个确定的初始值。但是局部变量不一样,如果一个局部变量定义了但没有赋初始值是不能使用的,字节码校验的时候也会被虚拟机发现而导致类加载失败!

public static void main(String[] args) {
int value;
System.out.println(value);  //程序编译失败,未给局部变量附初始值
}

2.2 操作数栈

  操作数栈是一个后入先出栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到方法表的 Code 属性的 max_locals 数据项中。操作数栈的每一个元素可以是任意的 Java 数据类型,包括 long 和 double;

  当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。

2.3 动态链接

  每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,只有这个引用是为了支持方法调用过程中的动态链接(Dynamic Linking)。Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态链接。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态链接!

  当前线程的栈帧通过获取方法的直接引用,指向着常量池对应方法的字节码,就可以利用常量池、操作数栈执行方法!

2.4 方法返回地址

当一个方法开始执行后,只有两种方式可以退出这个方法。

第一种:执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口;

第二种:在方法的执行过程中遇到了异常(Exception),并且议程没有在方法体中处理,简称异常完成出口;

无论何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。

三、方法调用

  方法调用并不等于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。在程序运行时,进行方法调用是最普遍、最频繁的操作,但Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于上面说的直接引用)。这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,设置到运行期间再能确定目标方法的直接引用!

3.1 解析

  只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法。

3.2 静态分派和动态分派

首先明白一点:Java语言是一种静态多分派,动态单分派语言!

静态分派(方法重载关联)

  变量本身的静态类型不会被改变,静态类型在编译期可知,而实际类型变化的结果在运行期才可确定。静态方法会在类加载期就进行解析,而静态方法显然也是可以拥有重载版本的,选择重载版本的过程也是通过静态分派完成的。

动态分派(方法的重写关联)

invokevirtual指令的多态查找过程,运行时解析过程分为:

  由于动态分派是非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法,因此在虚拟机的实际实现中基于性能的考虑,大部分实现都不会真正地进行如此频繁的搜索。面对这种情况,最常用的“稳定优化”手段就是为类在方法区中建立一个虚方法表(Virtual  Method  Table,也称为itable),使用虚拟机表索引来代替元数据以提高性能!

四、基于栈的字节码解释执行引擎

4.1 解释执行

Java语言经常被人定义为“解释执行”的语言!

编译原理的简单过程:词法分析 --> 语法分析 --> 语义分析和中间代码的产生 --> 优化 --> 目标代码生成!

  Java语言中,javac 编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。这一部分动作是在Java虚拟机之外进行的,而解释器在虚拟机的内部,所以Java程序的编译就是半独立的实现!

4.2 基于栈的指令集

4.3 基于栈的解释器执行过程

一段简单的算术代码:

public int calc(){
int a = 100;
int b = 200;
int c = 300;
return (a + b) * c;
}

字节码指令表示:

public int calc();

Code:

Stack=2, Locals=4, Args_size=1 //操作栈深度为2和4个Slot局部变量表

0:bipush 100  //将100压入操作数栈

2:istore_1  //将栈顶100数值存放到局变量Slot,index=1中

3:sipush 200  //将200压入操作数栈

6:istore_2     //将栈顶200数值存放到局部变量Slot,index=2中

7:sipush 300  //将300压入操作数栈

10:istore_3  //将栈顶200数值存放到局部变量Slot,index=3中

11:iload_1  //将index=1的局部变量表数值压入操作数栈(100)

12:iload_2  //将index=2的局部变量表数值压入操作数栈(200)

13:iadd  //取栈顶两个数值相加,结果压入操作数栈(300)

14:iload_3  //将index=3的局部变量表数值压入操作数栈(300)

15:imul       //取栈顶两个数值相乘,结果压入操作数栈(90000)

16:ireturn  //取栈顶数值返回调用者结果

图解展示:

  

JVM字节码执行引擎的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. JVM学习笔记:字节码执行引擎

    JVM学习笔记:字节码执行引擎 移步大神贴:http://rednaxelafx.iteye.com/blog/492667  

  7. 深入理解JVM虚拟机5:虚拟机字节码执行引擎

    虚拟机字节码执行引擎   转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...

  8. JVM基础结构与字节码执行引擎

    JVM基础结构 JVM内部结构如下:栈.堆. 栈 JVM中的栈主要是指线程里面的栈,里面有方法栈.native方法栈.PC寄存器等等:每个方法栈是由栈帧组成的:每个栈帧是由局部变量表.操作数栈等组成. ...

  9. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

随机推荐

  1. fedora30平台安装docker 19.03

    一,下载docker 1,说明:docker的打包对于fedora的支持很及时, 所以在fedora 30/31上都可以直接使用官方的rpm包 下载地址: https://download.docke ...

  2. buuctf-misc-刷新过的图片

    知识点:F5隐写 kali中安装F5-steganography 工具 git clone https://github.com/matthewgao/F5-steganography 解密的时候输入 ...

  3. Java 8 中的抽象类和接口到底有啥区别?

    上一篇栈长发了这篇<Java 8 有多牛逼?打破一切你对接口的认知!>,帮助许多人解开了疑惑,还有读者留言说两者还有啥区别,故引发了此篇: 在我们面试时也会经常遇到面试官问抽象类和接口的区 ...

  4. spring-boot-route(二十二)实现邮件发送功能

    在项目开发中,除了需要短信验证外,有时候为了节省 短信费也会使用邮件发送.在Spring项目中发送邮件需要封装复杂的消息体,不太方便.而在Spring Boot项目中发送邮件就太简单了,下面一起来看看 ...

  5. Phoenix的一些问题

    date: 2020-09-10 13:50:00 updated: 2020-09-14 16:30:00 1. Phoenix索引 全局索引:适合读多写少的场景.写数据时因为索引表分布在不同数据节 ...

  6. (python)getattr等用法

    getattr() 函数用于返回一个对象属性值; 语法 getattr(object, name[, default]) 参数 object -- 对象. name -- 字符串,对象属性. defa ...

  7. Centos 7 firewall 命令

    Centos 7 firewall 命令: 查看已经开放的端口: firewall-cmd --list-ports 开启端口 firewall-cmd --zone=public --add-por ...

  8. Mongodb命令 --- MongoDB基础用法(二)

    Mongodb命令 数据库操作 创建数据库 MongoDB 创建数据库的语法格式如下: use DATABASE_NAME 如果数据库不存在,则创建数据库,否则切换到指定数据库. 删除数据库 Mong ...

  9. CodeForces 916D Jamie and To-do List

    题意 你需要维护一个任务列表,有 \(q\) 次操作,每次操作形如以下四种: set a x:设置任务 \(a\) 的优先级为 \(x\),如果任务列表中没有 \(a\) 则加进来. remove a ...

  10. 宝塔面板无法进入phpadmin管理数据库解决办法

    ECS--华为云 宝塔面板6.0 phpMyAdmin 4.4 经过搜索发现问题在于端口和安全组规则 解决方法如下 1.在安全中添加888端口 2.华为云安全组规则--入方向规则--添加规则 3.打开 ...