JVM虚拟机执行引擎

java的执行引擎是虚拟机自己提供的,所以更加灵活可以支持应将不支持的指令集。(对硬件指令集的封装 :P)

栈帧

jvm执行方法调用的数据结构,一个方法从开始执行到结束执行是一次入栈出栈的过程。
在便宜Class文件时候局部变量表占用内存的大小和栈帧深度都已经决定好了。
一个方法执行调用链会很长,只有当前的线程中调用栈栈顶的栈帧才能被执行,该栈帧被称为当前栈帧(Current Frame)对应的方法是当前方法(Current Method),栈帧结构如图:

栈帧结构]

局部变量表

总共有byte、short、int、short、boolean、float、refrence、returnAddress 8种。

在编译期就在方法的Code属性max_locals就决定了大小。局部变量表的大小单位是Slot,虚拟机规定者byte、short、int、boolean、refrence、returnAddress 数据类型可以用32位或者更小的空间存储,即4byte。如果是64位系统采用8byte但是需要用数据补齐等方式让其看起来是32位的。
long、double则采用64位空间存储即8byte,注意这里虽然是用俩个32位空间存储但是因为是局部变量,但是是建立在线程的栈上没有共享问题是线程安全的。
refrence要遵循俩个协议

  1. 能找到堆中对象的数据起始位置
  2. 能找到方法去中Class类的位置

    参数值到参数变量列表的转换,如果不是static方法,局部变量表第0位给隐藏变量“this”,参数分别占第1~n位。剩下的会分配给方法内生命的局部变量。局部变量的slot可以重用。

    导致的一个“坑”是假如当前的变量后面不需要了,如果后面没有对变量的声明,修改局部变量,在方法执行期间gc是不会回收该局部变量,所以有一些比较耗内存的变量在用完了后,会“a=null”,加速gc的回收

    局部变量不赋值是不能使的,因为没有向static变量有个准备阶段赋0值

操作栈

后入先出,保存栈帧运行时候的数据,比如运算操作,方法调用等,实际上方法的执行就是操作栈不同的入栈/出栈。最大值在方法的属性Code规定的max_stack中在编译时候操作栈的深度就规定好了。栈元素的数据类型必须与自己吗指令序列严格匹配。

动态链接

栈帧都包含一个指向运行时常量池中,该栈帧所对应的方法的引用,用于在运行时将一些符号应用初始化成直接引用的操作。

接口返回地址

  1. Nomal Method Invocation Completion。即正常的方法
  2. Abrupt Method Invocation Completion。即出现异常的返回

无论何种返回都会退回到上层调用者的位置,正常返回有可能将返回值给调用者,调用者会将返回值压入自己的操作栈中。异常返回无返回值。

方法的推出等同于当前的栈帧出栈,同时把返回值压入调用者的操纵栈,修改PC指向方法调用的后面一条指令

方法调用

方法调用有5个指令

invokestatic:静态方法
invokespecial:构造方法,私有方法,父类方法
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时制定一个接口的实现类
invokedynmatic:运行时动态解析所应用的方法–这是用户设计的应到方法决定的

解析调用

只在编译期就能决定,且不会在运行时修改的方法,这里指invokestatic、invokespecial指令调用的方法,还有final的方法。

分派

由于java面向对象特性中的多态,导致编译器无法决定使用哪个版本需在运行期指定方法版本的方法。

类A a=new B()。B是A的子类或者接口实现类,那么A是静态类型,B是真实类型。即父类、接口是静态类型,实现类、子类是真实类型。

静态分派

如果方法的版本取决于类的静态类型,我们称为静态分派。主要场景用于重载见下面的方法。发生在编译阶段,因为在编译时候只能确认静态版本,但是在实际调用中我们只能通过真是的类型去推断选择一个合理版本

动态分派

如果确认方法的版本去介于类的动态类型,我们称为动态分派。主要场景用于重写。原理是当我们在方法中声明一个对象时候,会把对象的实际数据(引用)压入操作栈,当需要调用方法时候,取出来的是实际数据调用的就是实际数据的方法版本。发生在运行期。

单分派和多分派

当一个方法有俩个宗树的静态、动态分派时候就是多分派。

jvm实现动态分派的方法

由于动态分派需要在class元数据中分析查找方法,为了提高性能,jvm在方法区简历了vtable(虚方法表),当调用指令invokeinterface时候会直接通过vtable的索引查找需要的方法版本

vtable–virtual interface

虚方法表,由于动态分派非常频繁,jvm为了提高性能,在方法区为类创建了虚方法表,虚方法表保存的是类方法的真实地址,如果遇到没重写父类的方法地址就是父类的地址。一般在类加载的链接阶段,当类初始化0值完成方法表也初始化完成

动态类型语言支持

动态类型语言主要是通过java.lang.invoke包支持。他和反射的区别是,发射时模拟类的创建和调用过程,而invoke模拟的是操作码

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

java的执行引擎是基于操作栈的,解释执行。与其相对的还有编译执行如图:

编译原理.png

java的解释器和解释执行在jvm中,抽象语法树之前的步骤都独立于jvm虚拟机,所以java是半独立解释性语言。

基于栈或者基于寄存器

  • 基于寄存器的优缺点:优点:直接操作寄存器,不直接操作内存,CPU执行性能高性能高。缺点:由于直接在寄存器操作依赖硬件移植性差
  • 基于栈的优缺点:优点:移植性好,通过操作栈不直接操作寄存器,代码更为紧凑,简单。缺点:效率比基于寄存器低

解释执行过程

将数据先push到操作栈中在pop到局部变量表,在从局部变量表load数据计算然后push到操作栈中。