正文

执行引擎是 Java 虚拟机最核心的组成部分之一。在不同的虚拟机实现里,执行引擎在执行 Java 代码时可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择,也可能两者兼备。但从外观上看,所有 Java 虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。

物理机与虚拟机的执行引擎:

  • 物理机的执行引擎:直接建立在处理器、硬件、指令集和操作系统层面上。
  • 虚拟机的执行引擎:由自己实现,可自行制定指令集与执行引擎的体系结构,能够执行那些不被硬件直接支持的指令集格式。

一、运行时栈帧结构

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。

对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。

1、局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

局部变量表的容量以变量槽(Variable Slot,下称 Slot)为最小单位,虚拟机规范中并没有明确指明一个 Slot 应占用的内存空间大小。为了尽可能节省栈帧空间,局部变量表中的 Slot 是可以重用的

虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从 0 开始至局部变量表最大的 Slot 数量。

在方法执行时,虚拟机通过局部变量表完成参数值到参数变量列表的传递。如果执行的是实例方法(非 static 方法),那局部变量表中第 0 位索引的 Slot 默认用于传递方法所属对象实例的引用,在方法中可通过关键字“this”访问到这个隐含的参数。其余参数则按照参数表顺序排列,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的 Slot。

2、操作数栈

操作数栈(Operand Stack)也称为操作栈,它是一个后入先出的栈。操作数栈的每一个元素可以是任意的 Java 数据类型。

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

在概念模型中,两个栈帧作为虚拟机栈的元素,是完全独立的。但在大多数虚拟机的实现里会做一些优化处理,令两个栈帧出现一部分重叠。这样在进行方法调用时就可以共用一部分数据,无须进行额外的参数复制传递。

3、动态连接

每个栈帧都包含一个指向运行时常量池中,该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。

  • 静态解析:在类加载阶段或第一次使用时,将符号引用转化为直接引用。
  • 动态连接:在每一次运行期间,将符号引用转化为直接引用。

4、方法返回地址

两种退出方法的方式:

  • 正常完成出口: 执行引擎遇到任意一个方法返回的字节码指令,此时可能会有返回值传递给上层的方法调用者。
  • 异常完成出口: 方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,这种退出方式不会有返回值。。

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

一般来说,方法正常退出时,调用者的 PC 计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址要通过异常处理器表来确定,栈帧中一般不会保存这部分信息。

方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(有的话)压入调用者栈帧的操作数栈中,调整 PC 计数器的值以指向方法调用指令后面的一条指令等。

5、附加信息

虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与调试相关的信息。

二、方法调用

方法调用并不等于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。

一切方法调用在 Class 文件里存储的只是符号引用,而不是直接引用,只有在类加载期间,甚至是运行期间才能确定目标方法的直接引用。

方法调用字节码指令:

  • invokestatic:调用静态方法。
  • invokespecial:调用实例构造器 init() 方法、私有方法、父类方法。
  • invokevirtual:调用所有虚方法。
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
  • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。

1、解析

在类加载的解析阶段,将方法的符号引用转化为直接引用,这类方法调用称为解析。这种解析能成立的前提是:方法在程序执行之前有一个可确定的调用版本,并且这个方法的调用版本在运行期不可改变,即“编译器可知,运行期不可变”。

只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段确定唯一的调用版本,因此都能在类加载阶段被解析。这些方法称为非虚方法,与之相反,其他方法称为虚方法。

final 方法虽然是使用 invokevirtual 指令调用的,但由于它无法被覆盖,没有其他版本,所以无须对方法接收者进行多态选择。因此,fanal 方法也属于非虚方法。

2、分派

(1)静态分派

依赖静态类型(又称外观类型)来定位方法执行版本的分派动作,称为静态分派。静态分派的典型应用是方法重载。

静态类型是编译期可知的。

静态分派发生在编译阶段,因此确定静态分派的动作不是由虚拟机来执行的。

(2)动态分派

在运行期根据实际类型确定方法执行版本的分派过程,称为动态分派。动态分派的典型应用是方法重写。

实际类型是在运行期才可确定。

动态分派是非常频繁的动作,而且运行时需要在类的方法元数据中搜索合适的目标方法。基于性能的考虑,大部分的虚拟机实现都不会真正地进行如此频繁的搜索。最常用的“稳定优化”手段是为类在方法区中建立一个虚方法表,使用虚方法表索引来代替元数据查找以提高性能。

虚方法表中存放着各个方法的实际入口地址。

(3)单分派与多分派

根据分派基于多少种宗量,可以将分派划分为单分派和多分派。单分派是根据一个宗量对目标方法进行选择,多分派则是根据多余一个宗量对目标方法进行选择。

方法的接收者与方法的参数统称为方法的宗量。

静态分派是根据方法接收者的静态类型和方法参数来选择目标方法的,因此静态分派属性多分派类型。

动态分派只根据方法接收者的实例类型来选择目标方法,因此动态分派属于单分派类型。

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

1、编译过程

如今,基于物理机、虚拟机的语言,大多都会遵循基于现代经典编译原理的思路,在执行前先对程序源码进行词法分析和语法分析处理,把源码转化为抽象语法树。

对于一门具体语言的实现来说,词法分析、语法分析以及后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是 C/C++ 语言。也可以选择把其中的一部分(如生成抽象语法树之前的步骤)实现为一个半独立的编译器,这类代表是 Java 语言。又或者把这些步骤和执行引擎全部集中封装在一个封闭的黑匣子之中,如大多数的 JavaScript 执行器。

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

2、解释执行

Java 语言经常被定位为“解释执行”的语言,在 Java 初生的 JDK1.0 时代,这种定义还算准确,但当主流的虚拟机中包含了即时编译器后,Class 文件中的代码到底会被解释执行还是编译执行,就成了只有虚拟机自己才能准确判断的事情。

3、基于栈的指令集与基于寄存器的指令集

Java 编译器输出的指令流,基本上是一种基于栈的指令集架构,指令流中的指令大部分是零地址指令,它们依赖操作数栈进行工作。与之相对的另外一套常用的指令集架构是基于寄存器的指令集,最典型的就是 x86 的二地址指令集,这些指令依赖寄存器进行工作。

  • 基于栈的指令集:可移植,但执行速度相对较慢。
  • 基于寄存器的指令集:执行速度快,但由于寄存器由硬件直接提供,程序不可避免要受到硬件的约束。

《深入理解 Java 虚拟机》读书笔记:虚拟机字节码执行引擎的更多相关文章

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

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

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

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

  3. 深入理解Java虚拟机06--虚拟机字节码执行引擎

    一.前言 物理机的执行引擎是直接在物理硬件如CPU.操作系统.指令集上运行的,但是对于虚拟机来讲,他的执行引擎由自己实现. 执行引擎有统一的外观(Java虚拟机规范),不同类型的虚拟机都遵循了这一规范 ...

  4. 《深入理解Java虚拟机》学习笔记之字节码执行引擎

    Java虚拟机的执行引擎不管是解释执行还是编译执行,根据概念模型都具有统一的外观:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 栈帧(Stack Frame) ...

  5. Java之深入JVM(6) - 字节码执行引擎(转)

    本文为转载,来自 前面我们不止一次的提到,Java是一种跨平台的语言,为什么可以跨平台,因为我们编译的结果是中间代码—字节码,而不是机器码,那字节码在整个Java平台扮演着什么样的角色的呢?JDK1. ...

  6. 深入理解Java虚拟机读书笔记5----虚拟机字节码执行引擎

    五 虚拟机字节码执行引擎   1 运行时栈帧结构     ---栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素.     ---栈帧中存储了方法的局部变 ...

  7. 深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

    目录 1.类文件结构 1.1 Class类文件结构 1.2 魔数与Class文件的版本 1.3 常量池 1.4 访问标志 1.5 类索引.父索引与接口索引集合 1.6 字段表集合 1.7 方法集合 1 ...

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

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

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

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

  10. 深入理解Java虚拟机(字节码执行引擎)

    深入理解Java虚拟机(字节码执行引擎) 本文首发于微信公众号:BaronTalk 执行引擎是 Java 虚拟机最核心的组成部分之一.「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力, ...

随机推荐

  1. servlet之间传递数据的方式

    Servlet传递数据方式 基本概述 Servlet传递数据的方式有很多,这里提供五种方式: 1.静态变量 2.HttpServletResponse的sendRedirect()方法 3.HttpS ...

  2. springboot学习笔记:7.IDEA下5步完成热部署配置

    开发工具IDEA 2017.02   JDK1.8 1.pom.xml中增加: <dependency> <groupId>org.springframework.boot&l ...

  3. selenium元素定位(一)

    Selenium提供了8种定位方式. id name class name tag name link text partial link text xpath css selector 这8种定位方 ...

  4. 虚拟机apache启动

    /usr/local/apache2/bin/apachectl restart 重启 当启动也行 尝试过进入目录运行,比较奇怪,www目录竟然不一致,直接使用 server httpd start ...

  5. jquery框架概览(二)

    (function(window, undefined) { })(window) window对象作为参数传进闭包的好处 JavaScript 全局对象.函数以及变量均自动成为 window 对象的 ...

  6. [LC] 211. Add and Search Word - Data structure design

    Design a data structure that supports the following two operations: void addWord(word) bool search(w ...

  7. git本地仓库目录问题

    git安装后修改默认的路径:每次打开git bash后都会进入这个目录 https://blog.csdn.net/weixin_39634961/article/details/79881140 在 ...

  8. Jsp入门EL表达式_学习笔记

    1.EL表达式 [1] 简介 > JSP表达式 <%= %> 用于向页面中输出一个对象. > 到JSP2.0时,在我们的页面中不允许出现 JSP表达式和 脚本片段. > ...

  9. Nginx笔记:支持对用户提交URL和服务的URL不一致时,保持对POST提交的支持

    用户访问的URL和服务的URL不一致,需要对URL修改,同时使用的是POST提交方式 location ~* ^/portalproxy/([-]*)/portal$ { #rewrite '^/po ...

  10. Inventor 卸载工具,完美彻底卸载清除干净Inventor各种残留注册表和文件

    一些同学安装Inventor出错了,也有时候想重新安装Inventor的时候会出现这种本电脑windows系统已安装Inventor,你要是不留意直接安装,只会安装Inventor的附件,Invent ...