定义

java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。在不同的虚拟机实现里,执行引擎在执行java代码的时候可能会有解释执行和编译执行两种选择,也可能两者兼备。

运行时栈帧结构

java字节码执行引擎在调用和执行方法的时候使用了一种叫做栈帧的数据结构。

在jvm的内存结构里,存在这一块称为虚拟机栈的内存区域,虚拟机栈中的元素就是栈帧。每个方法的调用至结束对应着一个栈帧在虚拟机栈的入栈和出栈。

栈帧数据结构示意图:

如图所示,因为虚拟机栈是线程私有的,所以每个线程都有自己的虚拟机栈;而每个线程的虚拟机栈中都有多个栈帧对应一个方法调用链中的多个方法,栈顶的栈帧是当前正在执行的方法;每个栈帧主要包含4个部分:

  1. 局部变量表
  2. 操作数栈
  3. 动态连接
  4. 方法返回地址

在编译代码的阶段,栈帧中需要多大的局部变量表,多深的操作数栈都是已经完全确定的,而且会写入到方法表的Code属性中。

接下来一次介绍栈帧中的4个部分:

局部变量表

局部变量表是一组变量值存储空间,用于存放方法参数和方法内局部变量。

局部变量表的容量以变量槽Slot为最小单位,规定一个Slot可以存放一个32位以内的数据类型,java中占32位以内的数据类型有8种基本数据除了long和double以外的6种,还有reference和returnAddress,共8种类型。而对于64位的数据类型,即long和double,则会以高位对齐的方式为其分配两个连续的Slot空间。

在方法执行时,方法的参数列表是通过局部变量表传递的,如果执行的是实例方法(非static方法),则局部变量表中的第0位索引的Slot默认保存方法所属对象的引用,以支持“this”关键字来访问对象数据。其余参数则按参数列表顺序,占用从1开始的局部变量Slot,参数表分配完成后,在根据方法体内局部变量定义的顺序和作用域为局部变量分配其余Slot。之所以这里要强调作用域是因为为了节省栈帧空间,局部变量表中的Slot是可以重用的,当一个Slot中保存的局部变量超出其作用域后,这个Slot可以被复用来存储新的变量。

操作数栈

操作数栈是用来执行字节码命令的,比如iadd命令在运行的时候,就是把操作数栈中最接近栈顶的2个元素出栈相加,然后将结果入栈。

操作数栈的每一个元素可以是任意的java数据类型,32位数据所占栈容量为1,64位数据所占栈容量为2。

java虚拟机的解释执行引擎是“基于栈的执行引擎”,这里的栈就是指操作数栈。

动态连接

一个指向运行时常量池的引用,用来支持当前方法的代码实现动态链接。

方法返回地址

一个方法开始后,只有两种方式可以退出,一种是当执行引擎遇到任意一个返回字节码指令的时候,另一种是在执行中遇到异常并且没有捕获的时候。无论以哪种方式退出,退出后都需要返回到方法被调用的位置,因此需要在栈帧中保存一些信息,用来恢复它上层方法的执行状态。

方法调用

方法调用不等于方法执行,方法调用阶段的唯一目的就是确定被调用方法的版本。

解析

我们知道,在虚拟机进行类加载的时候,有一个阶段叫做“解析”,在解析阶段会将常量池中的一部分符号引用转化为直接引用,在这个阶段,有一部分方法调用就已经被解析为了直接引用,解析的前提是,这部分方法的调用目标在编译阶段就可以确定下来。

在java虚拟机中共有5个方法调用指令:

  1. invokestatic
  2. invokespecial
  3. invokevirtual
  4. invokeinterface
  5. invokedynamic

可以在“解析”阶段就确认调用目标的有invokestatic和invokespecial指令调用的方法以及invokevirtual调用的final方法,这些方法被称为“非虚方法”,与之相反的被称为“虚方法”。

分派

分派分为“静态分派”和“动态分派”。

静态分派

静态分派用来实现java语言的"重载"特性。

当我们声明一个变量时,可能会用到这种形式:Human man=new Man();(Man是Human子类),对于man这个变量来说,Human叫做它的静态类型,Man叫做它的实际类型。

"重载"是基于静态类型。也就是说,对于相同名称,参数列表不同的方法,选择哪个方法取决于参数列表中参数的静态类型,而静态类型在编译时是可知的,因此静态分派发生在编译阶段。

动态分派

动态分派用来实现java语言的“重写”特性,动态分派对应invokevirtual命令。

invokevirtual命令的执行过程如下:

  1. 找到操作数栈栈顶的第一个元素所指向的对象(也被称为方法的接收者)实际类型,记为C。
  2. 如果在C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限验证,如果通过则返回这个方法的直接引用,查找结束;如果不通过,则返回java.lang.IllegalAccessError异常。
  3. 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
  4. 如果最终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

我们可以看出invokevirtual指令执行的时候是由对象的实际类型决定调用哪个方法版本的,而对象的实际类型只有到运行期的时候才能确定,我们把这种在运行期确定方法版本的分派过程称为“动态分派”。

单分派和多分派

方法的接收者与方法的参数统称为方法的宗量,根据分派基于宗量的多少,将分派分为单分派和多分派两种。

目前的java语言中,静态分派的时候,是基于接收者和方法参数两个宗量进行的,因此静态分派是多分派;动态分派的时候,是基于接收者一个宗量进行的,因此动态分派是单分派。

方法执行

一般从程序代码到物理机可以运行的目标代码都要经历下图所示的过程:

图中展示了编译执行和解释执行这两种路径,无论是哪种执行方式,一般都会先通过词法分析和语法分析把源代码转化为抽象语法树(AST)。

然后从抽象语法树节点开始,中间的分支代表的是解释执行过程,下边的分支代表的是编译执行过程。

jvm执行字节码是采用的解释执行,javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。而解释器则在虚拟机的内部。

javac编译器输出的字节码指令流是一种基于栈的指令集架构,指令流中的大部分指令都是零地址指令,它们依赖操作数栈进行工作。与之相对的是基于寄存器的指令集。

基于栈的指令集有以下优点:

  1. 可移植
  2. 代码更加紧凑(不需要存放参数)
  3. 编译器实现更简单(不需要考虑空间分配问题,所需空间都在栈上操作)

基于栈的指令集的主要缺点就是执行速度慢:一个是因为基于栈完成相同功能所需要的指令集数量一般比基于寄存器要多,因为入栈、出栈会产生多余的指令数量;另一个是因为栈是基于内存实现的,内存的读取速度本身就比处理器寄存器要慢,这一点可以采取栈顶缓存的手段,把最常用的操作映射到寄存器中避免直接访问内存,但是毕竟只是优化措施,不能解决本质问题。

执行方法中的代码本质就是通过栈的指令集来进行运算,这里就不详述了。

java虚拟机字节码执行引擎的更多相关文章

  1. 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性

    我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...

  2. Java虚拟机-字节码执行引擎

    概述 Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade).不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行. 运行时帧栈结构 栈帧(Sta ...

  3. 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的

    概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...

  4. Java虚拟机--虚拟机字节码执行引擎

    Java虚拟机--虚拟机字节码执行引擎 所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 用于支持虚拟机进行方法调用和方 ...

  5. JAVA虚拟机:虚拟机字节码执行引擎

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

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

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

  7. 深入了解java虚拟机(JVM) 第十三章 虚拟机字节码执行引擎

    一.概述 执行引擎是java虚拟机最核心的组成部件之一.虚拟机的执行引擎由自己实现,所以可以自行定制指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式.所有的Java虚拟机的执行 ...

  8. 【Java】JVM(六)虚拟机字节码执行引擎

    一.概述 执行引擎是虚拟机中最核心的部分之一, 虚拟机自己实现引擎,自己定义指令集和执行引擎的结构体系. 二.栈帧 栈帧包含(1)局部变量表.(2)操作数栈.(3)动态链接.(4)方法返回地址.(5) ...

  9. 《java虚拟机》----虚拟机字节码执行引擎

    No1: 物理机的执行引擎是直接建立在处理器.硬件.指令集合操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格 ...

随机推荐

  1. BZOJ4873:[SHOI2017]寿司餐厅——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=4873 https://www.luogu.org/problemnew/show/P3749 简要题 ...

  2. HDOJ(HDU).1035 Robot Motion (DFS)

    HDOJ(HDU).1035 Robot Motion [从零开始DFS(4)] 点我挑战题目 从零开始DFS HDOJ.1342 Lotto [从零开始DFS(0)] - DFS思想与框架/双重DF ...

  3. 在Linux系统的服务器上使用Memtester进行内存压力测试

    最近要测试一台机器的整体性能情况,就在google搜索一番,发现这个一个小工具,说是可以进行内存的压力测试,Memtester主要是捕获内存错误和一直处于很高或者很低的坏位, 其测试的主要项目有随机值 ...

  4. ACE线程管理机制-并发控制(4)

    转载于:http://www.cnblogs.com/TianFang/archive/2006/12/04/581857.html ACE Synchronization类 这一类并发控制对象一般也 ...

  5. POJ 2763 Housewife Wind 纯粹LCA写法(简单无脑)

    Description After their royal wedding, Jiajia and Wind hid away in XX Village, to enjoy their ordina ...

  6. hadoop压缩和解压

    最近有一个hadoop集群上的备份需求.源文件有几百G,如果直接复制太占用磁盘空间.将文件从hadoop集群下载到本地,压缩之后再上传到hadoop则太耗时间.于是想到能否直接在HDFS文件系统上进行 ...

  7. HDU1255 扫描线 矩形交面积 离散化

    覆盖的面积 Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Subm ...

  8. HDU1711 KMP(模板题)

    Number Sequence Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  9. main函数的传参与返回

    1.谁给main函数传参(1)调用main函数所在的程序的它的父进程给main函数传参,并且接收main的返回值.2.为什么需要给main函数传参(1)首先,main函数不传参是可以的,也就是说父进程 ...

  10. linux 命令后台运行(转载)

    原文连接:https://www.cnblogs.com/lwm-1988/archive/2011/08/20/2147299.html 有两种方式: 1. command & : 后台运行 ...