一、概述

  在不同的虚拟机实现里面,执行引擎在执行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. Linux命令之{ }花括号

    括号扩展:{ } {} 可以实现打印重复字符串的简化形式 [10:04:14 root@C8[ 2020-06-16DIR]#echo file{1,3,5} file1 file3 file5 [1 ...

  2. 通俗的讲解Python中的__new__()方法

    2020-3-17更新本文,对本文中存争议的例子进行了更新! 曾经我幼稚的以为认识了python的__init__()方法就相当于认识了类构造器,结果,__new__()方法突然出现在我眼前,让我突然 ...

  3. C++学习---顺序表的构建及操作

    #include<iostream> #include<fstream> using namespace std; #define MAXLEN 100 //定义顺序表 str ...

  4. pychartdir模块安装

    python模块pychartdir导入问题 在迁移别人写好的脚本时,发现pychartdir没有导入,脚本执行报错.以下是报错内容: [modps@LGJF-ZYC5-MMSC-WEB02 ~]$ ...

  5. hdu6115 Factory (LCA + 倍增)

    Factory Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Others)Total ...

  6. PowerShell类grep

    PowerShell类grep 方法一: windows下没有grep不过有findstr, 功能差不多 方法二: powershell自带的正择功能 xxx | where {$_ -match & ...

  7. Bitmap缩放(一)

    使用矩阵进行压缩,通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样.先用位图的option将位图压缩一半,再用matrix缩放0.3f public class MainActivity e ...

  8. 简简单单入个Redis的门

    Redis介绍 Redis是一种key-value的存储系统,它是一种nosql(Not Only [SQL])非关系型的数据库,它支持string(字符串).list(链表).set(集合).has ...

  9. U138097 小鱼吃大鱼 埃氏筛

    题目描述 小P同学在养殖一种非常凶狠的鱼,而且与其他鱼类不同,这种鱼越大越温顺,反而小鱼最凶残.当两条鱼相遇时, 小鱼会不断撕咬大鱼,每一口都咬下与它自身等重的肉(小鱼保持体重不变),直到大鱼的体重小 ...

  10. drf 权限校验设置与源码分析

    权限校验 权限校验和认证校验必须同时使用,并且权限校验是排在认证校验之后的,这在源码中可以查找到其执行顺序. 权限校验也很重要,认证校验可以确保一个用户登录之后才能对接口做操作,而权限校验可以依据这个 ...