jvm-虚拟机字节码执行引擎(六)

jvm-虚拟机字节码执行引擎(六)

运行时栈帧结构

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
栈帧存储了:局部变量,操作数栈,动态连接和方法返回地址等信息。

局部变量

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

类变量会有两次初始化:第一次系统赋值初始化,第二次用户自己的赋值。但是局部变量系统给他初始化,如果用户没有给局部变量初始化,是不能使用的。

方法调用

方法调用不等同于方法执行,方法调用阶段唯一的任务就是确定被调用的是哪一个方法,这里暂时不涉及到内部具体的运作过程。

Class方法的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是实际运行时内存布局的入口地址(相对于直接引用来说)

解析

所有的方法调用在Class文件里面存储的都只是符号引用,在类加载的阶段会将其中的一部分符号引用转化为直接引用,这种解析需要满足的条件是:方法在程序真正运行之前就有一个可确定的调用版本,并且方法调用版本在运行期间是不可改变的。

说白了就是程序写好,在编译器进行编译时就一定能确定下来,这类方法的调用称为解析。

符合上面说的主要包括:静态方法和私有方法两大类,他们都符合类加载阶段进行解析

静态方法,私有方法,实例构造器,父类方法,它们在类加载的时候就会把符号引用解析为该方法的直接引用。

分派

java面向对象的三个特性:继承,封装,多态。分派调用的过程将会揭示多态的特征。

静态分派

package com.apricotforest.cloudfollowup.person.controller;

public class TestStatic {

    static abstract class Human {

    }

    static class Man extends Human {
    }

    static class Woman extends Human {
    }

    public void sayHello(Human human) {
        System.out.println("sya human");
    }

    public void sayHello(Man man) {
        System.out.println("sya man");
    }

    public void sayHello(Woman woman) {
        System.out.println("sya woman");
    }

    public static void main(String orgs[]) {
        TestStatic t = new TestStatic();
        Human man = new Man();
        Human woman = new Woman();
        t.sayHello(man);
        t.sayHello(woman);
        //sya human
        //sya human

        t.sayHello((Man)man);
        t.sayHello((Woman)woman);
        //sya man
        //sya woman


    }


}

静态分派的最直接的解释是在重载的时候是通过参数的静态类型而不是实际类型作为判断依据的。因此在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本。

首先弄清楚什么是静态类型和实际类型,在语句Human man = new Man();中,Human称为变量的静态类型(Static Type)或者外观类型(Apparent Type),Man称为变量的实际类型(Actual Type)。静态类型和实际类型在程序中都可以发生变化,但是静态类型的变化仅仅发生在使用时,变量本身的静态类型不会改变,最终的静态类型在编译期是可知的;而实际类型变化的结果在运行期才可以确定,编译时并不知道一个对象的实际类型是什么。
但是Javac编译期在重载时是通过参数的静态类型而不是实际类型作为判定依据的,man和woman的静态类型都是Human。

动态分派

动态分派的一个最直接的例子是重写(Override)。对于重写,我们已经很熟悉了,那么Java虚拟机是如何在程序运行期间确定方法的执行版本的呢?

解释这个现象,就不得不涉及Java虚拟机的invokevirtual指令了,这个指令的解析过程有助于我们更深刻理解重写的本质。该指令的具体解析过程如下:

  • 找到操作数栈栈顶的第一个元素所指向的对象的实际类型,记为C
  • 如果在类型C中找到与常量中描述符和简单名称都相符的方法,则进行访问权限的校验,如果通过则返回这个方法的直接引用,查找结束;如果不通过,则返回非法访问异常
  • 如果在类型C中没有找到,则按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证过程
  • 如果始终没有找到合适的方法,则抛出抽象方法错误的异常
    从这个过程可以发现,在第一步的时候就在运行期确定接收对象(执行方法的所有者称为接受者)的实际类型,所以当调用invokevirtual指令就会把运行时常量池中符号引用解析为不同的直接引用,这就是方法重写的本质。

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81459995