Post

虚拟机字节码执行引擎

虚拟机字节码执行引擎

Owner: better

概述

“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件制约地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集格式。

之前还有Java是解释型还是编译型一看就是不理解JVM。。。。。

从外观上来看,所有的Java虚拟机的执行引擎输入、输出都是一致的:输入的是字节码二进制流,处 理过程是字节码解析执行的等效过程,输出的是执行结果,

运行时栈帧结构

  • Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,
  • 它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。
  • 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息

⼀个线程中的⽅法调⽤链可能会很⻓,以Java程序的⻆度来看,同⼀时刻、同⼀条线程⾥⾯,在调⽤堆栈的所有⽅法都同时处于执⾏状态。⽽对于执⾏引擎来讲,在活动线程中,只有位于栈顶的⽅法才是在运⾏的,只有位于栈顶的栈帧才是⽣效的,其被称为“当前栈帧”(Current Stack Frame),与这个栈帧所关联的⽅法被称为“当前⽅法”(Current Method)

局部变量表:局部变量表(Local Variables Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义局部变量。在Java程序被编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需分配的局部变量表的最大容量。

操作数栈:操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到Code属性的max_stacks数据项之中。操作数栈的每一个元素都可以是包括long和double在内的任意Java数据类型。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。Javac编译器的数据流分析工作保证了在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。

动态连接:

每个栈帧都包含一个指向运行时常量池[1]中该栈帧所属方法的引用,持有这个引用是为了支持方 法调用过程中的动态连接(Dynamic Linking)。我们知道Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。

方法返回地址:

当一个方法开始执行后,只有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者或者主调方法),方法是否有返回值以及返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为“正常调用完成”(Normal Method Invocation Completion)。

另外一种退出方式是在方法执行的过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理。无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为“异常调用完成(Abrupt Method Invocation Completion)”。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者提供任何返回值的。

方法调用

⽅法调⽤阶段唯⼀的任务就是确定被调⽤⽅法的版本 (即调⽤哪⼀个⽅法) ,⼀切⽅法调⽤在Class⽂件⾥⾯存储的都只是符号引⽤,⽽不是⽅法在实际运⾏时内存布局中的⼊⼝地址(也就是之前说的直接引⽤)。

解析: 所有⽅法调⽤的⽬标⽅法在Class⽂件⾥⾯都是⼀个常量池中的符号引⽤。⽅法在程序真正运⾏之前就有⼀个可确定的调⽤版本,并且这个⽅法的调⽤版本在运⾏期是不可改变的。换句话说,调⽤⽬标在程序代码写好、编译器进⾏编译那⼀刻就已经确定下来。这类⽅法的调⽤被称为解析(Resolution)。

在Java语⾔中符合“编译期可知,运⾏期不可变”这个要求的⽅法,主要有静态⽅法和私有⽅法两⼤类,前者与类型直接关联,后者在外部不可被访问,这两种⽅法各⾃的特点决定了它们都不可能通过继承或别的⽅式重写出其他版本,因此它们都适合在类加载阶段进⾏解析。

分派:

  1. 静态分派:所有依赖静态类型来决定方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用表现就是方法重载。
  2. 动态分派
  3. 单分派与多分派:根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种。单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。
This post is licensed under CC BY 4.0 by the author.