一、概述

  在不同的虚拟机实现里面,执行引擎在执行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. selenium--数据填充

    from time import sleep from selenium import webdriver br = webdriver.Chrome() url = "https://ww ...

  2. 第六章 Linux系统之文件管理

    一.文件管理概述 1.对文件做些什么? 谈到Linux文件管理,首先我们需要了解的就是,我们要对文件做些什么事情? 其实无非就是对一个文件进行创建.复制.移动.查看.编辑.压缩.查找.删除等等 2.内 ...

  3. <!DOCTYPE>,<address>,<applet>的用法

    希望以下内容能让大家有所收获 HTML <!DOCTYPE> 标签 实例 <!DOCTYPE html> <html> <head> <title ...

  4. 《Kafka笔记》3、Kafka高级API

    目录 1 Kafka高级API特性 1.1 Offset的自动控制 1.1.1 消费者offset初始策略 1.1.2 消费者offset自动提交策略 1.2 Acks & Retries(应 ...

  5. OpenCV计算机视觉学习(7)——图像金字塔(高斯金字塔,拉普拉斯金字塔)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 本节 ...

  6. SpringMVC异常的处理机制

    SpringMVC异常的处理机制 处理流程图 其本质还是把异常交给SpringMVC框架来处理 系统的dao.service.controller出现异常都通过throws Exception向上抛出 ...

  7. Callable接口

    Callable与Runnable的不同区别在于: 1.Callable有返回值 Runnable没有返回值 2.Callable需要实现的方法是call方法       Runnable需要实现的方 ...

  8. Linux常用系统文件目录结构

    Linux常用系统文件目录结构 bin:全称binary,含义是二进制.该目录中存储的都是一些二进制文件,文件都是可以被运行的. dev:该目录主要存放的是外接设备,例如硬盘.其他的光盘等.在其中的外 ...

  9. 【5】TensorFlow光速入门-图片分类完整代码

    本文地址:https://www.cnblogs.com/tujia/p/13862364.html 系列文章: [0]TensorFlow光速入门-序 [1]TensorFlow光速入门-tenso ...

  10. shell脚本之字符串测试表达式

    1.字符串测试操作符 字符串测试操作符的作用有:比较两个字符串是否相同.字符串的长度是否为零,字符串是否为NULL(注:bash区分零长度字符串和空字符串等) 下表为常用字符串操作符 也可以通过man ...