执行引擎

执行引擎是java虚拟机的重要组成部分。它的作用是接收字节码,解析字节码,执行并输出执行结果。

虚拟机是相对于物理机的概念,物理机的执行引擎是直接建立在处理器、寄存器、指令集和操作系统的层面上的。虚拟机的执行引擎是JVM自己实现的。所以可以定制指令集和执行引擎的结构体系。

运行时栈帧结构

栈帧是支持虚拟机进行方法调用和方法执行的数据结构。它存储在运行时数据区的虚拟机栈中。

每一个方法的从开始到完成的过程,都对应了一个栈帧的入栈和出栈的过程。

一个栈帧包含了:局部变量表,操作数栈,动态连接,方法返回地址。

局部变量表和操作数栈在编译的时候,已经可以完全确定,并且写入到了Class文件的方法表的Code属性之中。因此一个栈帧需要多大的内存,不会受到程序运行期的变量数据影响。

局部变量表

用来存放方法参数和方法中的局部变量。

如果是占用内存比较大的对象,在使用结束但是作用域还有其他执行比较长的语句之前,可以把它置为null,然后就可以被gc。但是不建议对所有的对象都这个处理,没有必要的地方不需要有这么多的类似代码。

同时,使用JIT编译执行的时候,赋null值的操作将会被抹除。

局部变量不会被赋予初始值,所以必须初始化才能使用。

操作数栈

一个后进先出的栈。

我理解的,局部变量用于存储,操作数用于计算。比如执行一个加法操作,需要将两个数值压入操作数栈顶,调用其他方法的时候,可以通过操作数栈来传递参数。

动态连接

每个方法都包含一个指向运行时常量池中的方法引用。持有这个引用(这里应该是指符号引用)就可以支持在方法调用的过程中动态连接。

方法调用

不等同于方法执行,目的是为了确定方法的版本。可以说,就是确定方法的直接引用。

非虚方法

Class文件中的方法引用都是常量池中的符号引用,在类加载的解析操作中,部分符号引用将会白转换为直接引用。

这些可以在解析阶段转换的方法:

需要满足:编译器可知,运行期不可变。这种方法叫非虚方法。

非虚方法:静态方法,私有方法,构造器,父类方法,final方法。

分派

描述虚拟机如何定位要执行的方法。

  • 静态分派:编译阶段的分派,根据变量声明的类型来确定要执行的方法。重载方法的选择使用静态分派。
  • 动态分派:运行阶段的分派,根据变量实际的类型来确定要执行的方法。重写方法的选择使用动态分派。

静态分派:

编译阶段需要定义方法的符号引用,符号引用用于描述一个方法,描述的度量是方法名和方法参数。如果有方法重载,这个时候就需要编译器选择一个合适的符号引用,这个阶段是在编译期做的,所以只能使用参数的声明类型来识别,称为静态分派。

  1. public class StaticDispatch {
  2.     static abstract class Human{
  3.     }
  4.     static class Man extends Human{
  5.     }
  6.     static class Woman extends Human{
  7.     }
  8.     public static void sayHello(Human guy){
  9.         System.out.println("hello,guy!");
  10.     }
  11.     public static void sayHello(Man guy){
  12.         System.out.println("hello,gentlemen!");
  13.     }
  14.     public static void sayHello(Woman guy){
  15.         System.out.println("hello,lady!");
  16.     }
  17.  
  18.     public static void main(String[] args) {
  19.         Human man=new Man();
  20.         Human woman=new Woman();
  21.         sayHello(man);
  22.         sayHello(woman);
  23.     }
  24. }

输出:

hello,guy!
hello,guy!

动态分派:在编译期确定了静态分派的方法符号引用之后,在运行的时候,还要根据调用方法的实际对象类型来确定要调用那个类的符合这个符号引用的方法。动态分派将会根据方法的符号引用,在运行时的实际对象类型中寻找方法,如果找不到,会去超类寻找。

  1. public class DynamicDispatch {
  2.     static abstract class Human{
  3.         protected abstract void sayHello();
  4.     }
  5.     static class Man extends Human{
  6.         @Override
  7.         protected void sayHello() {
  8.             System.out.println("man say hello!");
  9.         }
  10.     }
  11.     static class Woman extends Human{
  12.         @Override
  13.         protected void sayHello() {
  14.             System.out.println("woman say hello!");
  15.         }
  16.     }
  17.     public static void main(String[] args) {
  18.  
  19.         Human man=new Man();
  20.         Human woman=new Woman();
  21.         man.sayHello();
  22.         woman.sayHello();
  23.         man=new Woman();
  24.         man.sayHello();
  25.     }
  26. }

输出:
man say hello!
woman say hello!
woman say hello!

方法重载的选择:

  1. public class LiteralTest {
  2.     /**/
  3.     public static void sayHello(char arg){
  4.         System.out.println("hello char");
  5.     }
  6.     public static void sayHello(int arg){
  7.         System.out.println("hello int");
  8.     }
  9.  
  10.     public static void sayHello(long arg){
  11.         System.out.println("hello long");
  12.     }
  13.  
  14.     public static void sayHello(Character arg){
  15.         System.out.println("hello Character");
  16.     }
  17.     public static void main(String[] args) {
  18.         sayHello('a');
  19.     }
  20. }

因为传入的是一个char类型,所以会选择第一个方法,如果删除第一个方法,将会选择第二个,以此类推,优先级为第1 2 3 4 个方法。也就是说,最后才选择装箱。如果有可变参数的方法,那么它将至绝对的最后一个选择。

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

使用解释的方式来执行字节码方法。

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

java编译器输出的指令流,基本上是一种基于栈的指令集架构,它们依赖操作数栈进行工作,与之相对的另外一套常用的指令集架构是基于寄存器的指令集,最典型的就是x86的二地址指令集,这些指令依赖寄存器进行工作。那么基于栈的指令集和基于寄存器的指令集在这两者之间的不同:

例如,分别使用这两种指令集去计算"1+1"的结果,基于栈的指令计算过程:

iconst_1

iconst_1

iadd

istore_0

两个iconst_1指令连续的把两个常量1压入栈后,iadd指令把栈顶的两个值出栈并相加,然后把结果放回栈顶,最后istore_0把栈顶的值放到局部便量表的第0个slot中。

如果是基于寄存器的指令集,程序会是这样的:

mov eax,1

add eax,1

mov指令把EAX寄存器的值设为1,然后add指令再把这个值加1,结果就保存在EAX寄存器中。

基于栈的指令集最主要的优点就是可移植性,寄存器由硬件直接提供,程序直接依赖这些硬件寄存器则不可避免的要受到硬件的约束。

栈架构指令集的主要缺点是执行速度相对来说稍慢一些。栈架构指令集的代码虽然紧凑,但是完成相同功能所需的指令数量一般会比寄存器架构多,因为出栈、入栈操作本身就产生了相当多的指令。更重要是栈实现在内存中,频繁的栈访问也就意味着频繁的内存访问,相对于处理器来说,内存始终是执行速度的瓶颈,尽管虚拟机可以采用栈顶缓存的手段,把最常用的操作映射到寄存器中以避免直接内存访问,但这也只是优化措施而不是解决本质问题的方法,因此,由于指令数量和内存访问的原因,导致了栈架构指令集的执行速度相对较慢。

基于栈的解释器执行过程

  1. public int calculate(){
  2.     int a = 100;
  3.     int b = 200;
  4.     int c = 300;
  5.     return (a + b) * c;
  6. }

我们编译代码后使用javap -verbose命令查看字节码指令,具体字节码代码如下所示:

  1. public int calculate();
  2.   Code:
  3.    Stack=2, Locals=4, Args_size=1
  4.    0: bipush 100
  5.    2: istore_1
  6.    3: sipush 200
  7.    6: istore_2
  8.    7: sipush 300
  9.    10: istore_3
  10.    11: iload_1
  11.    12: iload_2
  12.    13: iadd
  13.    14: iload_3
  14.    15: imul
  15.    16: ireturn
  16.   LineNumberTable:
  17.    line 3: 0
  18.    line 4: 3
  19.    line 5: 7
  20.    line 6: 11
  21.  
  22. }

根据字节码可以看出,这段代码需要深度为2的操作数栈(Stack=2)和4个Slot的局部变量空间(Locals=4)。下面,使用7张图片来描述上面的字节码代码执行过程中的代码、操作数栈和局部变量表的变化情况。

上图展示了执行偏移地址为0的指令的情况,bipush指令的作用是将单字节的整型常量值(-128~127)推入操作数栈顶,后跟一个参数,指明推送的常量值,这里是100。

上图则是执行偏移地址为1的指令,istore_1指令的作用是将操作数栈顶的整型值出栈并存放到第1个局部变量Slot中。后面四条指令(3、6、7、10)都是做同样的事情,也就是在对应代码中把变量a、b、c赋值为100、200、300。后面四条指令的图就不重复画了。

上面展示了执行偏移地址为11的指令,iload_1指令的作用是将局部变量第1个Slot中的整型值复制到操作数栈顶。

上图为执行偏移地址12的指令,iload_2指令的执行过程与iload_1类似,把第2个Slot的整型值入栈。

上图展示了执行偏移地址为13的指令情况,iadd指令的作用是将操作数栈中前两个栈顶元素出栈,做整型加法,然后把结果重新入栈。在iadd指令执行完毕后,栈中原有的100和200出栈,它们相加后的和300重新入栈。

上图为执行偏移地址为14的指令的情况,iload_3指令把存放在第3个局部变量Slot中的300入栈到操作数栈中。这时操作数栈为两个整数300,。

下一条偏移地址为15的指令imul是将操作数栈中前两个栈顶元素出栈,做整型乘法,然后把结果重新入栈,这里和iadd指令执行过程完全类似,所以就不重复画图了。

上图是最后一条指令也就是偏移地址为16的指令的执行过程,ireturn指令是方法返回指令之一,它将结束方法执行并将操作数栈顶的整型值返回给此方法的调用者。到此为止,该方法执行结束。

注:上面的执行过程只是一种概念模型,虚拟机最终会对执行过程做出一些优化来提高性能,实际的运作过程不一定完全符合概念模型的描述。不过从这段程序的执行过程也可以看出栈结构指令集的一般运行过程,整个运算过程的中间变量都是以操作数栈的出栈和入栈为信息交换途径。

深入理解JAVA虚拟机 虚拟机字节码执行引擎的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  10. java虚拟机字节码执行引擎

    定义 java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果.在不同的虚拟机实现里,执行引擎在执行java代码 ...

随机推荐

  1. KahnProcessNetwork的Python实现

    用Pytho实现了一个Kahn Process Network: 思路: 用Python的list模拟queue. 每个channel一个queue 用一个list (fgLog)来记录所有push到 ...

  2. golang开发问题

    开发问题: How to find out which types implement which interface in Golang? How do you quickly find the i ...

  3. Jmeter(六)事务

    事务是性能测试之必不可少的关注点, Jmeter默认把每一个请求都统计成了一个事务, 但有时候我们根据业务需求, 会把多个操作统计成一个事务, Jmeter当然也考虑到了这个需求, 因此我们可以通过逻 ...

  4. flask包request搭建微服务(模拟测试桩)

    from flask import Flask,requestimport json app=Flask(__name__)@app.route('/outsideWeb/integration/qr ...

  5. 正确关闭selinux

    .查看当前selinux的状态命令为 getenforce .两个都要关.注意先看看有么有这两个文件,如果没有就创建一个,否则后期会出现很多问题 cat > /etc/selinux/confi ...

  6. DocX 在文档中插入图片时,为什么不能按实际设置的大小插入,而Spire.Doc却可以

    我的目标目标要求:将一个图片插入到页面中,页面边界为0,使用下面的代码去实现(button1UseDocX_Click函数),生成的文档不能达到目的.而使用Spire.Doc却能达到目的button1 ...

  7. Day06:方法 / 猜字母游戏

    JAVA方法 方法就是处理一个业务所需要编写的代码的代码段 方法特性 一个方法处理一个业务 方法代码编写,不和其他方法冲突 方法定义后可以随意调用 将main方法中的所有代码分散到各个普通方法中 减少 ...

  8. Ubuntu环境配置机器安装驱动

    ubuntu_environment_config.md thead > tr > th { text-align: left; border-bottom: 1px solid; } t ...

  9. jQ的toggle() 方法

    语法:$(selector).toggle(speed,callback,switch) 实例: <script src="js/jquery.min.js">< ...

  10. [转帖]VMWare官网:无法关闭 ESXi 主机上的虚拟机 (1014165)

    无法关闭 ESXi 主机上的虚拟机 (1014165) https://kb.vmware.com/s/article/1014165?lang=zh_CN Last Updated: 4/17/20 ...