JVM学习笔记——节码执行引擎

简述

执行引擎是Java虚拟机最核心的组成部分之一,,全部的Java虚拟机的执行引擎都是一致的:
输入:字节码文件
处理:字节码解析
输出:执行结果java

运行时栈帧结构

在介绍虚拟机栈时就提到,每一个方法在执行的同时都会建立一个栈帧用于存储局部变量表、操做数栈、动态连接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧中须要多大的局部变量表和多深的操做数栈在编译代码的过程当中已经彻底肯定,并写入到方法表的Code属性中。在活动的线程中,位于当前栈顶的栈帧才是有效的,执行引擎运行的全部字节码指令只针对当前栈帧进行操做。jvm


方法调用

一切方法调用在Class文件里面都是一个常量池中的符号引用。在类加载解析阶段,可能会将其中一部分符号引用转化为直接引用,也有可能会在初始化阶段以后再开始,取决于方法在运行以前是否有有肯定的调用版本,且在运行期间不变。

在Java中符合编译器可知,运行期不可变的方法,主要包括静态方法和私有方法两大类,它们都不可能经过某些方式重写其余版本,所以它们都适合在类加载阶段进行解析

JVM提供了5条方法调用字节码:
invokestatic:调用静态方法
invokespecial:调用实例构造器方法、私有方法和父类方法
invokevirtual:调用全部的虚方法
invokedynamic:运行时动态解析出调用点限定符所引用的方法,再执行该方法
只要能被invokestaticinvokespecial指令调用的方法,均可以在解析阶段中肯定惟一的调用版本,它们在类加载时会把符号引用解析为该方法的直接引用。

示例:
spa

  • invokestatic
  • public class Person {
        public static void sayHello() {
            System.out.println("hello world");
        }
        public static void main(String[] args) {
            sayHello();
        }
    }
    复制代码
    javap -verbose查看


  • invokespecial
  • public class Person {
        public static void main(String[] args) {
            new Person();
        }
    }
    复制代码
    javap -verbose查看

    经过invokestatic和invokespecial指令调用的方法称为非虚方法,与之相反,其余方法称为虚方法(除去final方法),虚方法的调用是一个分派的过程,有静态也有动态,可分为静态单分派、静态多分派、动态单分派和动态多分派。线程

    静态分派

    全部依赖静态类型来定位方法执行版本的分派,静态分派的典型应用是方法重载,静态分派发生在编译阶段,所以静态分派的动做不禁虚拟机执行。eg:3d

    public class Person {
        public void sayHello(Object obj){
            System.out.println("hello Object");
        }
        public void sayHello(String str){
            System.out.println("hello String");
        }
        public static void main(String[] args) {
            Person p = new Person();
            Object obj = new String();
            p.sayHello(obj);
        }
    }
    复制代码

    结果:code

    hello Object
    复制代码

    相对于变量obj,Object是其静态变量,String是其实际变量,在编译阶段,Java编译器会根据参数的静态类型决定调用哪一个重载版本。用javap -verbose再看下
    cdn

    动态分派

    动态分派根据实际类型肯定方法执行版本对象

    public class Person {
        public static void main(String[] args) {
            Object p = new Person();
            Object obj = new String("比利时");
            System.out.println(obj.toString());
            System.out.println(p.toString());
        }
    }
    复制代码

    输出blog

    比利时
    jvm.Person@63ce0e18
    复制代码

    同一个静态类型,调用toString方法,结果彻底不一样,缘由就是由于这两个变量的实际类型不一样。经过java -verbose 查看字节码指令
    继承

    2一、31行指令将以前存放到局部变量表一、2位置的对象引用(接受者)压入操做数栈的栈顶,2二、32行是方法调用指令,虽然指令同样都是Object.toString,可是这两个指令最终执行的目标方法不相同
    ①.找到操做数栈的栈顶元素所指向的对象的实际类型,记为C
    ②.若是C中存在描述符和简单名称都相符的方法,则进行访问权限验证,若是验证经过,则直接返回这个方法的直接引用,不然返回java.lang.IllegalAccessError异常
    ③.若是C中不存在对应的方法,则按照继承关系对C的各个父类进行第2步的操做
    ④.若是各个父类也没对应的方法,则抛出异常
    因此上述两次invokevirtual指令将相同的符号引用解析成了不一样对象的直接引用,这个过程就是Java语言中重写的本质

    如何实现?
    为类在方法区中创建虚方法表,虚方法表中存放着各个方法的实际入口地址,若是某个方法在子类中没有被重写,那子类的虚方法表中的地址入口和父类相同方法的地址入口一致,都指向父类的实现入口。

    以上面代码为例,Person类没有重写toString()方法,因此toString()方法指向Object类型数据,而String重写了toString()方法,因此没有( 未列Object其余方法)。

    感谢

    《深刻理解Java虚拟机》

    相关文章
    相关标签/搜索