深入理解java虚拟机——虚拟机字节码执行引擎

执行引擎是 Java 虚拟机最核心的组成部分之一。所有的 Java 虚拟机的执行引擎都是一致的:输入字节码文件,执行字节码解析的等效过程,输出结果。

1. 运行时栈帧结构

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。编译程序代码时,栈帧需要分配的内存已经确定,不会受到程序运行期变量数据的影响。

  • 局部变量表:存放方法参数和方法内部定义的局部变量。以变量槽(Slot)为最小单位。变量槽有索引,第 0 位索引默认是传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问到这个隐含的参数。局部变量和类变量不同,变量定义了但没有赋初始值是不能使用的。
  • 操作数栈:方法刚开始时栈是空的,各种字节码指令往操作栈中写入和提取内容。例如:算数运算通过操作栈实现;调用其它方法时通过通过操作数栈传递参数。
  • 动态连接:指向运行时常量池中该栈帧所属方法的引用,为了支持方法调用过程中的动态连接。
  • 方法返回地址:方法退出时,需要返回上层调用的位置。由此处保存。

2. 方法调用

方法调用在 Class 文件里面存储的都是符号引用,需要在类加载期间,甚至到运行期间才能确定内存的入口地址(直接引用)

2.1 解析

在解析阶段,会将一部分符号引用转化为直接引用,前提是方法在运行前有一个可确定的调用版本,并且在运行期不可变。这类方法的调用称为解析。符合这个条件的有静态方法、私有方法、实例构造器、父类方法 4 类。

// 方法静态解析
public class StaticResolution {

    public static void sayHello() {
        System.out.println("hello world");
    }

    public static void main(String[] args) {
        StaticResolution.sayHello();
    }

}

2.2 分派

分派调用可能是静态的也可能是动态的,根据分派依据的宗量数可分为单分派和多分派。

2.2.1 静态分派

静态分派发生在编译阶段,典型应用是方法重载。

// 方法静态分派
public class StaticDispatch {

    static abstract class Human {
    }

    static class Man extends Human {
    }

    static class Woman extends Human {
    }

    public void sayHello(Human guy) {
        System.out.println("hello,guy!");
    }

    public void sayHello(Man guy) {
        System.out.println("hello,gentleman!");
    }

    public void sayHello(Woman guy) {
        System.out.println("hello,lady!");
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch sr = new StaticDispatch();
        sr.sayHello(man);
        sr.sayHello(woman);
    }
}
/**
输出:
hello,guy!
hello,guy!
**/

在Human man = new Man(); 中,“Human”称为变量的静态类型,“Man”则称为变量的实际类型。静态类型在编译期可知,实际类型变化的结果在运行期才能确定。所以,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
所以在编译阶段,Javac 编译器会根据参数的静态类型决定使用哪个重载版本。实际上,调用的目标是sayHello(Human)。

//重载方法匹配优先级
public class Overload {

    public static void sayHello(Object arg) {
        System.out.println("hello Object");
    }

    public static void sayHello(int arg) {
        System.out.println("hello int");
    }

    public static void sayHello(long arg) {
        System.out.println("hello long");
    }

    public static void sayHello(Character arg) {
        System.out.println("hello Character");
    }

    public static void sayHello(char arg) {
        System.out.println("hello char");
    }

    public static void sayHello(char... arg) {
        System.out.println("hello char ...");
    }

    public static void sayHello(Serializable arg) {
        System.out.println("hello Serializable");
    }

    public static void main(String[] args) {
        sayHello('a');
    }
}

匹配的优先级为:char,int,long,Character,Serializable,Object,char …

2.2.2 动态分派

动态分派和重写关联密切。

// 方法动态分派
public class DynamicDispatch {

    static abstract class Human {
        protected abstract void sayHello();
    }

    static class Man extends Human {
        @Override
        protected void sayHello() {
            System.out.println("man say hello");
        }
    }

    static class Woman extends Human {
        @Override
        protected void sayHello() {
            System.out.println("woman say hello");
        }
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();
        man = new Woman();
        man.sayHello();
    }
}
/**
输出:
man say hello
woman say hello
woman say hello
**/

运行步骤:

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记住 C。
  2. 如果在类型 C 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回 java.lang.IllegalAccessError 异常。
  3. 否则,按照继承关系从下往上依次对 C 的各个父类进行第 2 步的搜索和验证过程。
  4. 如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodError 异常。

这种运行时动态的确定执行版本的过程,称为动态分派。

2.2.3 单分派与多分派

方法的接受者与方法的参数统称为方法的宗量。单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。

public class Dispatch {

    static class QQ {}

    static class _360 {}

    public static class Father {
        public void hardChoice(QQ arg) {
            System.out.println("father choose qq");
        }

        public void hardChoice(_360 arg) {
            System.out.println("father choose 360");
        }
    }

    public static class Son extends Father {
        public void hardChoice(QQ arg) {
            System.out.println("son choose qq");
        }

        public void hardChoice(_360 arg) {
            System.out.println("son choose 360");
        }
    }

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

/**
输出:
father choose 360
son choose qq
**/

静态分派需要静态类型和参数确定,所以是多分派的。而动态分派已经确定了参数信息,只需要确定方法接受者的实际类型,所以动态分派是单分派。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值