虚拟机字节码执行引擎

前言

物理机处理引擎是建立在处理器,硬件,指令集,和操作系统层面上,而虚拟机执行是自行指定指令集和执行引擎结构体系。

运行时栈帧结构

栈帧是虚拟机运行时数据区中虚拟机栈的元素,存储了方法局部变量表,操作数栈,动态连接和方法返回地址等信息,在程序编译的时候,栈帧的大小就已经确定好了;一个线程的方法调用栈很长,但是只有栈顶的栈帧才是活动中的。

局部变量表

操作数栈 操作数栈的每一个元素可以是任意的java数据类型,方法运行期,各种字节码指令往操作数栈中写入和提取内容。 操作数栈中元素的数据类型必须与字节码指令序列严格匹配

动态链接 每个栈帧都包含一个指向运行时常量池的引用,从而支持方法在调用过程中的动态链接

方法返回地址 退出一个方法执行有两种方式,一种方式是方法执行过程中遇到返回的字节码指令,通过把返回值传递给上层方法调用者来结束在调用方法内的执行过程,另一种在调用方法执行过程中遇到异常,而且这个异常没有得到处理,从而异常退出的方式,这种方式不会给上层调用者返回任何值。

方法调用

方法调用的目的是确定被调用的方法版本。

解析调用 虚拟机在类加载过程中对方法调用有一部分是在解析阶段将符号引用转化为直接引用,而其他的则是在运行期间才会得到最终的目标方法的直接引用。前者大多是私有方法,静态方法,构造器,父类方法,final修饰方法这些在运行期前就能确定的调用版本。详情见-> jvm类加载机制

分派调用

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

而在java的世界里,重载版本可能不是唯一的,只能是最合适

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');
    }
}

输出结果很明显会是hello char,但是如果依次从大到小入参去掉对应的方法,会出现不一样的输出结果。这个过程中会发生自动类型转换,装箱,查找父类接口等操作

输出结果

man say hello 
woman say hello
woman say hello

整个查找最终执行方法过程如下

上述代码是我们很常见的方法重写,我们把这样的方法调用方式成为动态分派

单分派,多分派

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

方法的接受者和方法的参数统称方法的宗量 上面选择执行方法过程如下:

综上所述,可以得到今天的java是一门静态多分派,动态单分派的语言

运行时动态分派需要在类的方法元数据中搜索合适的目标方法,基于性能的考虑,java虚拟机在方法区中建立一个虚方法表,来代替元数据的查找提高性能

动态语言类型

基于寄存器的指令集 java编译器输出的指令流,是一种基于栈的指令集架构,还有一种基于寄存器的指令集,最典型的就是现在大多数主流pc使用的X86指令集,大致区别如下: