当前位置:网站首页>JVM学习(五) -执行子系统

JVM学习(五) -执行子系统

2020-11-09 12:09:53 IT迷途小书童

虚拟机和物理机的区别。两种都有代码执行能力。物理机的执行引擎是建立在处理器、硬件、指令集和操作系统上。而虚拟机的执行引擎是有自己实现的。因此可以自行的制定指令集和执行引擎的结构关系。

个人理解:分为三个部分。分别是介绍运行时分的各个内存区域【程序计数器、java虚拟机栈【局部变量表、操作数栈、动态链接、方法返回地址】、本地方法栈、方法区、堆】。方法的调用【解析、分派】、方法的执行【解析执行、编译执行】

1. 运行时栈帧结构

栈帧是用于虚拟机进行方法调用和方法执行的数据结构。是虚拟机栈的栈元素。这部分内容和运行内存区域中JAVA虚拟机栈中的栈帧结构部分有重叠的部分。一个栈帧包括局部变量表、操作数栈、动态链接和方法返回地址四部分。栈帧需要多大的局部变量表和多深的操作数栈在编译阶段都已经确定的

Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中的栈指的是操作数栈。

程序计数器、虚拟机栈、本地方法栈都是线程级别的。跟着现成的生而生、跟着线程的灭而灭。意味着,每个线程都有自己的java虚拟机栈。该线程中调用的方法都作为一个栈帧,压栈和弹栈。

(1) 局部变量表

主要用来存储方法的参数和方法内部定义的的局部变量。局部变量表是一组值存储空间。

一个方法只有在调用完成后才会释放。因而,如果一个方法之前定义了大内存的对象,但是,在方法后半部分已经不需要这部分内存了。同时,后半部分需要执行很长时间。这种情况下,会造成前半部分创建的大容量的对象既不被使用需要,又因为方法因为没有执行完成而释放。这部分内存会一致占着不能被释放。所以,也可以在方法内,将不需要继续使用的变量赋值为null。可以让垃圾回收器及时的回收。

(2) 操作数栈

操作数栈的深度在编译的时候已经确定。在方法执行的过程中,从局部变量表中获取数据,压栈。需要计算的时候根据运算符号从栈顶弹栈获得数据进行计算,将结果压栈。

(3) 动态链接

class文件中,一个方法需要调用其它方法。需要将这些方法的符号引用转换成内存地址中的直接引用。而这些符号引用存在方法区的常量池中

每个栈的栈帧都包含一个指向运行时常量池中该方法的符号引用。持有这个引用的目的是支持方法调用中的动态链接。

这些符号引用一部分会在类加载阶段或者第一次使用的时候转换成了直接引用。这种转换称为静态解析。另一部分将在每一次运行期间转化成直接引用。这部分称为动态链接。

(4) 方法返回地址

当一个方法开始执行时,可能有两种方式退出该方法正常完成出口异常完成出口。

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

2. 方法调用

方法调用并不等同于方法执行,核心任务是:确定被调用方法的版本【即调用的是哪一个方法】。不涉及方法内部的具体执行运行过程。

所有方法之间的调用在class文件中存储的是符号引用。而不是具体方法【实际运行时内存布局的入口地址】的直接引用。需要在类加载甚至在运行期间才能确定目标方法的直接引用。

(1) 解析

调用的目标方法都是方法区的常量池中的一个符号引用。如果程序在真正运行之前就可以确定方法调用的版本,并且这个方法调用的版本在运行期间是不会发生变化的。这种情境下,在类加载的解析阶段,会将符号引用转换成直接引用。这种方法的调用称之为解析。

符合“编译期可知,运行期不可变”的要求的方法,主要包括静态方法和私有方法两大类。前者与类直接关联,后者外部不可被访问。他们特点是不可能通过继承或者其它方式重写其它版本。因此,都会通过解析的方式进行方法调用。

解析对应5条方法调用指令:

① Invoke static :调用静态方法

② Invoke special:调用实例构造器<init>方法,私有方法和父类方法。

③ Invoke virtual:调用所有的虚方法。【final修饰的方法,因为不能被覆盖,没有其它版本,所以,无需对方法进行多态选择】

④ Invoke Interface:调用接口方法(多态)

⑤ Invoke dynamic

解析是一个静态的过程,在类加载的链接阶段的解析阶段中,就会把涉及的符号引全部转换成直接引用。

(2) 分派

① 静态分派

所有依赖静态类型【参数的声明类型来定位方法版本的分派称为静态分派。

静态分派最典型的是重载。在编译阶段,根据参数的静态类型就可以确定执行的是哪个版本的方法。静态分派发生在编译阶段,因此,静态分配的动作不由虚拟机来执行。

② 动态分派

运行期间根据实际类型确定方法执行的版本的分派称为动态分派。

动态分派最典型的就是重写

JVMinvoke virtual指令了,这个指令的解析过程有助于我们更深刻理解重写的本质。该指令的具体解析过程如下

  1. 找到操作数栈栈顶的第一个元素所指向的对象的实际类型,记为C。【获得实际对象的类型】。
  2. 如果在类型C中找到与常量中描述符和简单名称都相符的方法,则进行访问权限的校验,如果通过则返回这个方法的直接引用,查找结束;如果不通过,则返回非法访问异常【在C中找和当前方法匹配的方法】
  3. 如果在类型C中没有找到,则按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证过程首先在子类中找,没有再在父类中找。和双亲委派相反,双亲委派优先在父类加载器中处理,而重载优先在子类中匹配
  4. 如果始终没有找到合适的方法,则抛出抽象方法错误的异常

③ 单分派和多分派

(3) 动态类型语言支持

3. 执行引擎

核心理解虚拟机如果执行方法中的字节码指令集。其实就是基于栈的字节码解释执行引擎做的事情。JAVA虚拟机执行代码的时候,都有解释执行【通过解释器执行】编译执行【通过编译器产生本地代码执行】两种选择

执行引擎:将字节码指令解析/编译为对应平台上本地机器指令。简而言之,执行引擎充当高级语言和机器码之间的翻译者。

其中,执行引擎中 解释器(interpreter)提供解释执行功能。JIT Completer 提供编译执行的功能。

 

 

 

(1) 解释执行

JVM得到字节码之后。通过解析器Interpreter解析成最终的机器码。

(2) 基于栈的指令集和基于寄存器的指令集

基于栈的指令集:将指令集保存在栈中。大部分是零地址指令集,依赖操作数栈进行工作。Java编译器输出的指令集是基于栈的指令集。

PC电脑支持的就是基于寄存器的指令集

(3) 基于栈的解释器执行过程

从指令集顺序执行,将局部变量经过操作数栈,最后在局部变量表中生成。运算的过程是从局部变量表获得数据,然后放入操作数栈。从操作数栈弹出进行计算。之后将结果压栈。

 

先将数放入操作数栈,然后将操作数栈中的数据弹出放入局部变量表中,这样一步步将局部变量表的变量赋值。运算过程中,从局部变量表中获取数据压入操作数栈中。然后根据指令,从操作数栈中弹出数据进行计算,之后将计算结果压栈。

(4) 编译执行

JVM平台提供一种及时编译技术,及时编译的目的是避免函数被解释执行。而是将整个函数体编译成机器码。每次函数执行时,直接执行编译后的机器码。这种方式能将效率大幅提升。

 

版权声明
本文为[IT迷途小书童]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/maopneo/p/13947809.html