首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

虚拟机字节码施行引擎

2012-10-16 
虚拟机字节码执行引擎执行引擎在执行JAVA代码的时候可以选择解释执行(通过解释器执行)和编译执行(通过即使

虚拟机字节码执行引擎

执行引擎在执行JAVA代码的时候可以选择解释执行(通过解释器执行)和编译执行(通过即使编译器产生本地代码执行)两种选择。

?

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表操作数栈动态连接方法返回地址等信息。每一个方法调用的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程

?

一个线程中的方法调用链可能很长,很多方法都同时处于执行状态,对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的,成为Curent Stack Frame。 这个栈帧所关联的方法称为当前方法(Current Method)。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。

?

局部变量表

用于存放方法参数和方法内部定义的局部变量,在编译成CLASS文件时,就在方法的CODE属性的max_locals数据项中确定了该方法所需要分配的最大局部变量表的容量。

?

局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机规范中并没有明确指出一个slot应占用的内存空间大小

?

一个Slot可以存放一个32位以内的数据类型,这些类型有:boolean,byte,char,short,int,float,reference和returnAddress。returnAddress是为字节码指令jsr,jsr_w和ret服务的,指向下一条字节码的地址。

?

对于64位数据,JVM会以高位在前的方式分配两个连续的Slot空间

JAVA明确规定的64位数据只有longdouble两种。reference可能是32也可能是64位

?

JVM通过索引定位的方式使用局部变量表,索引值的范围从0开始到局部变量表最大的SLOT数量。

?

在方法执行时,JVM使用局部变量表完成参数值到参数变量列表的传递过程。如果是实例方法,那么局部变量表的第0位索引的SLOT默认是用于传递方法所属对象实例的引用,在方法中可以通过“this"访问到这个隐含的参数。其余参数则按照参数表的顺序排列,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用于分配其余SLOT。

?

局部变量表中的SLOT是可以重用的,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那么这个变量对应的SLOT就可以交给其他变量使用

?

?

0: new #16;3; dup4: invokespecial #18;7; astore_18: new #19;11:dup12:invokespecial #21;15:astore_216:aload_117:invokevirtual #22;20:aload_221:invokevirtual #22;24:new #19;27:dup28:invokespecial #21;31:astore_132:aload_133:invokevirtual #2236:return
?0-15行是准备工作,用于生成对象,初始化对象,并将两个实例存放在第一和第二个局部变量表slot中。16和20行分别将刚创建的两个对象引用压到栈顶,这两个对象是将要执行的sayHello()方法的所有者,成为接收者。17和21行是方法调用指令,可见指令和参数都是一样的,都是invokevirtual常量池中第22项的常量---Human.sayHello()的符号引用,而结果是这两次调用的结果不同,原因是invokevirtual指令的运行时解析过程:
    找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,通过则返回该方法直接引用,不通过抛出java.lang.IllegalAccessError否则,按照继承关系从下往上一次对C的各个父类进行第二步的搜索和验证没找到合适方法,抛出java.lang.AbstractMethodError异常
由于第一步是解析成对象的实际类型,因此两次调用的结果不一样。这个顺序实际是:找到实际类型---在该类型中搜索--在该类型的继承结构中自下而上搜索---抛出异常
在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
单分派和多分派方法的接收者与方法的参数统称为方法的宗量
根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种
单分派是根据一个宗量对目标方法进行选择,多分派是根据多于一个宗量对目标方法进行选择。
(同时有重载和重写发生的情况)根据之前方法调用可能生成的4种字节码,找到对应方法可能生成的字节码,再根据字节码解析过程进行判断。
首先进行静态分派,生成相应的字节码,在常量池中生成对应的方法符号引用,这个过程根据了两个宗量进行选择(接收者和参数),因此静态分派是多分派类型。
再进行动态分派,将符号引用变成直接引用时,只对方法的接收者进行选择,因此只有一个宗量,动态分派是单分派。
虚拟机动态分派的实现由于动态分派是非常频繁的操作,因此在JVM具体实现中基于性能考虑,常常 做一些优化,最初那个用的“稳定优化”手段就是为类在方法去中建立一个虚方发表(vtable),于此对应,invokeinterface执行时也会用到接口方发表,itable。
虚方法表中存放着各个方法的实际入口地址,如果某个方法在子类中没有被重写,那么子类的虚方法表里的地址入口和父类相同方法的地址入口是一致的。如果子类重写了这个方法,子类方法表中的地址就会被替换为指向子类实现版本的入口地址。为了程序实现上的方百年,具有相同签名的方法,在父类、子类的虚方发表中都应当具有一样的索引序号,这样当类型变换时,仅需要变更要查找的方法表即可。方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。

基于栈的字节码解释执行引擎
Java编译器输出的指令流,基本上是一种基于栈的指令集架构,与之对应的是寄存器指令集架构。比较起来,基于栈的指令集主要的优点是可移植性。而寄存器架构有更好的性能。















?

热点排行