深入理解java虚拟机-第五章:虚拟机字节码执行引擎

来源:互联网 发布:抢涨停板软件 编辑:程序博客网 时间:2024/06/05 16:15

本章将介绍虚拟机如何调用方法

一、java虚拟机字节码执行引擎

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

    执行流程:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。

二、运行时栈帧结构

    a)用于支持虚拟机进行方法调用和方法执行的数据结构;

    b)虚拟机栈的栈元素;

    c)存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。

    d)每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在虚拟机里面从入栈到出栈的过程。

    e)编译时,栈帧需要多大的局部变量表、多深的操作数栈都已确定,写入至Code属性之中,一个栈帧需要分配多少内存不受运行期变量数据的影响。

    f)活动线程中只有栈顶的栈帧(当前栈帧)是有效的。


    2.1 局部变量表

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

    最小单位:变量槽(Variable Slot ),支持存放boolean、byte、char、short、int、float、reference或returnAddress类型的数据。

    局部变量没有所谓的“准备阶段”,相比较于来变量来说,类变量有两次赋值的过程,一次是准备阶段,赋予系统初始值,另一种是初始化阶段,赋予程序员定义的初始值。局部变量程序员没有赋予初始值的话是不能使用的。

    2.2 操作数栈

      后入先出。

      方法执行过程中,字节码指令向操作数栈中写入和提取内容,也就是入栈和出栈操作。

     2.3动态连接

      每个栈帧都包含一个指向运行时常量池中该栈帧所述方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用-静态解析。另一部分将在每次运行期间转化为直接引用,这部分称为动态连接。

      2.4  方法返回地址

      方法退出方式:a)正常完成出口---执行引擎遇到任意一个发那个发返回的字节码指令,b)方法执行过程中遇到异常。

     退出的过程实际等同于当前栈帧出栈,因此退出的操作有:

      1.恢复上层方法的局部变量表和操作数栈

      2.把返回值压人调用者栈帧的操作数栈中

      3.调整PC计数器的值以指向方法调用指令后面的一条指令。


    三、方法调用

     方法调用不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪个方法)

    3.1 解析

     所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用。

     前提:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的,换句话说,调用目标在程序代码写好,编译器进行编译时就 必须确定下来。

   适用:静态方法和私有方法。前者与类型直接关联,后者在外部不可被访问。

   Java虚拟机里面提供了四条方法调用字节码指令:

    a)invokestatic:调用静态方法。                                                                    

    b)invokespecial:调用实例构造器<init>方法、私有方法和父类方法。 

    c)invokevirtual:调用所有的虚方法。

    d)invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。

    符合a和b这两个条件的,都可以在解析前确定唯一的调用版本,符合这个条件的有静态方法,私有方法,实例构造器和父类方法四类。

    解析调用一定是一个静态的过程,在编译期间就完全确定,在累装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。

     3.2 分派

       


      public class StaticTest {

static abstract class Humen{
}
static class Men extends Humen{
}
static class Women extends Humen{
}

public void sayHello(Humen h ){
System.out.println(" Humen ");
}
public void sayHello(Men m){
System.out.println(" Men ");
}
public void sayHello(Women w){
System.out.println(" Women ");
}

public static void main(String[] args) {
StaticTest st = new StaticTest();
Humen h = new Men();//等号前为静态类型   等号后面为实际类型
Humen hh = new Women();//等号前为静态类型   等号后面为实际类型
st.sayHello(h);
st.sayHello(hh);
}
     }

    运行结果:

    Humen 
    Humen 

   为什么呢?

   Humen h = new Men();//等号前为静态类型   等号后面为实际类型

   静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型在编译期可知的;

   实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。

   针对这样的输出结果:编译器在重载时通过参数的静态类型决定使用那个重载版本。因为在编译阶段,静态类型是可知的。

   3.2.1静态分派--重载

   依赖静态类型来定位方法执行版本的分派动作。最典型的应用就是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。

   编译器确定出方法的重载版本在很多情况下都不是唯一的,只能确定出方法“更加适合的版本”。

    3.2.2动态分派--重写

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

     动态分派查找过程:

    1)找到操作栈顶的第一个元素所指向懂得对象的实际类型,记作C;

    2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限检验,如果通过则返回这个方法的直接引用,查找过程结束。不通过则返回java.lang.IllegalAccessErrir异常。

     3)否则,按照继承关系从下往上一次对C的各个父类进行第2步的搜索和验证过程。

     4)如果始终没有找到合适的方法,则抛出java.lang,AbstractMethodError异常。

    3.2.3单分派和多分派

     静态分派属于多份派;

     动态分派属于单分派;

     宗量:方法的接收者和方法的参数统称。

     

public class Zongliang {

static class QQ{}

static class T360{}

static class Father{

void chooice(QQ q){
System.out.println("Father choice QQ");
}
void chooice(T360 q){
System.out.println("Father choice T360");
}
}

static class Son extends Father{
void chooice(QQ q){
System.out.println("Son choice QQ");
}
void chooice(T360 q){
System.out.println("Son choice T360");
}
}

public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.chooice(new QQ());
son.chooice(new T360());
}


}

   运行结果:  

Father choice QQ
Son choice T360

   分析:进行静态分派时(编译阶段编译器的选择过程),依据等号左边 可匹配到静态类型是Father,等号右边方法参数是QQ和360,这次选择的最终产物是两条invokevirtual指令,两条指令的参数分别为常量池中指向Father.choice(T360)和Father.choice(QQ)方法的符号引用,这里的依据是有两个宗量;

    再看看运行阶段虚拟机的选择,即动态分派的过程,在执行  son.chooice(new T360());时,更准确的是执行这句代码所对应的invokevirtual指令时,由于编译期已经决定目标方法的签名必须为son.chooice(new T360());唯一可以影响虚拟机选择的因素只有此方法的接收者的实际类型是Father还是son(具体执行方法的对象称为接收者,等号右边为接收者)。因此只有一个宗量可以选择。

   3.2.4虚拟机动态分派的实现

   动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法,基于性能考虑,稳定优化:在类的方法区中建立一个虚方法表,与此对应,在invokeinterface执行时也会用到接口方法表,使用虚方法表索引代替元数据查找以提高性能。

   

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

虚拟机如何执行方法里面的字节码指令的?

。。。我也不知道!




阅读全文
0 0
原创粉丝点击