美文网首页
第八章 虚拟机字节码执行引擎

第八章 虚拟机字节码执行引擎

作者: 骊骅 | 来源:发表于2017-04-05 14:24 被阅读14次

    [目录]
    概述

    1 概述

    • 不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行通过解释器执行)和编译执行通过即时编译器产生本地代码执行)两种选择也可能两者兼备
    • 从外观上看起来,所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件处理过程字节码解析的等效过程输出的是执行结果
    • 本章将主要从概念模型的角度来讲解虚拟机的方法调用和字节码执行。

    2 运行时栈帧结构

    • 栈帧(Stack Frame)是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素,用于支持虚拟机进行方法调用和方法执行的数据结构

    • 存储了方法的局部变量表操作数栈动态连接方法返回地址等信息

    • 每一个方法从调用开始至执行完成的过程 **---------> **一个栈帧在虚拟机栈里面从入栈到出栈的过程

    • 编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中

    • 一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态(方法间互相调用)

    • 对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current StackFrame),与这个栈帧相关联的方法称为当前方法(Current Method)

    2.1 局部变量表

    • 局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法的Code属性max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。
    • 局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位
    • 一个Slot可以存放一个32位以内的数据类型,Java中占用32位以内的数据类型有booleanbytecharshortintfloatreferencereturnAddress种类型
    • reference类型表示对一个对象实例的引用,一是从此引用中直接或间接地查找到对象在Java堆中的数据存放的起始地址索引,二是此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息

    2.2 操作数栈

    • 操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。

    2.3 动态连接

    • 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)

    2.4 方法返回地址

    两种返回的方法:
    正常完成出口(Normal Method Invocation Completion)异常完成出口(Abrupt Method Invocation Completion)
    无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息

    3 方法调用

    方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。

    3.1 解析

    • 调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析(Resolution)
    • 第七章讲过:目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这
      种解析能成立的前提是方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的
    • 符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法私有方法两大类

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

    方法名 解释 备注
    invokestatic 调用静态方法
    invokespecial 调用实例构造器<init>方法、私有方法和父类方法
    invokevirtual 调用所有的虚方法
    invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象
    invokedynamic todo todo

    只要能被invokestaticinvokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法私有方法实例构造器父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用


    invokestaticinvokespecial指令调用的方法 称为非虚方法,与之相
    反,其他方法称为虚方法(除去final方法,后文会提到)

    3.2 分派

    3.2.1 静态分派

    • 定义: 依赖静态类型来定位方法执行版本的分派动作
    • 静态分派发生在编译阶段
    • 确定静态分派的动作实际上不是由虚拟机来执行的。

    示例代码

    package study8;
    
    /**
     * Created by haicheng.lhc on 05/04/2017.
     *
     * @author haicheng.lhc
     * @date 2017/04/05
     */
    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);
        }
    }
    
    

    结果分析:

    我们把上面代码中的“Human”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么.虚拟机(准确地说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的

    3.2.2.动态分派

    • (Override)有着很密切的关联

    示例代码

    package study8;
    
    /**
     * Created by haicheng.lhc on 05/04/2017.
     *
     * @author haicheng.lhc
     * @date 2017/04/05
     */
    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();
        }
    }
    
    

    解析
    使用javap分析

    Compiled from "DynamicDispatch.java"
    public class study8.DynamicDispatch extends java.lang.Object
      SourceFile: "DynamicDispatch.java"
      InnerClass:
       #9= #4 of #7; //Woman=class study8/DynamicDispatch$Woman of class study8/DynamicDispatch
       #11= #2 of #7; //Man=class study8/DynamicDispatch$Man of class study8/DynamicDispatch
       abstract #13= #12 of #7; //Human=class study8/DynamicDispatch$Human of class study8/DynamicDispatch
      minor version: 0
      major version: 50
      Constant pool:
    const #1 = Method   #8.#22; //  java/lang/Object."<init>":()V
    const #2 = class    #23;    //  study8/DynamicDispatch$Man
    const #3 = Method   #2.#22; //  study8/DynamicDispatch$Man."<init>":()V
    const #4 = class    #24;    //  study8/DynamicDispatch$Woman
    const #5 = Method   #4.#22; //  study8/DynamicDispatch$Woman."<init>":()V
    const #6 = Method   #12.#25;    //  study8/DynamicDispatch$Human.sayHello:()V
    const #7 = class    #26;    //  study8/DynamicDispatch
    const #8 = class    #27;    //  java/lang/Object
    const #9 = Asciz    Woman;
    const #10 = Asciz   InnerClasses;
    const #11 = Asciz   Man;
    const #12 = class   #28;    //  study8/DynamicDispatch$Human
    const #13 = Asciz   Human;
    const #14 = Asciz   <init>;
    const #15 = Asciz   ()V;
    const #16 = Asciz   Code;
    const #17 = Asciz   LineNumberTable;
    const #18 = Asciz   main;
    const #19 = Asciz   ([Ljava/lang/String;)V;
    const #20 = Asciz   SourceFile;
    const #21 = Asciz   DynamicDispatch.java;
    const #22 = NameAndType #14:#15;//  "<init>":()V
    const #23 = Asciz   study8/DynamicDispatch$Man;
    const #24 = Asciz   study8/DynamicDispatch$Woman;
    const #25 = NameAndType #29:#15;//  sayHello:()V
    const #26 = Asciz   study8/DynamicDispatch;
    const #27 = Asciz   java/lang/Object;
    const #28 = Asciz   study8/DynamicDispatch$Human;
    const #29 = Asciz   sayHello;
    
    {
    public study8.DynamicDispatch();
      Code:
       Stack=1, Locals=1, Args_size=1
       0:   aload_0
       1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
       4:   return
      LineNumberTable:
       line 9: 0
       line 22: 4
    
    
    public static void main(java.lang.String[]);
      Code:
       Stack=2, Locals=3, Args_size=1
       0:   new #2; //class study8/DynamicDispatch$Man
       3:   dup
       4:   invokespecial   #3; //Method study8/DynamicDispatch$Man."<init>":()V
       7:   astore_1
       8:   new #4; //class study8/DynamicDispatch$Woman
       11:  dup
       12:  invokespecial   #5; //Method study8/DynamicDispatch$Woman."<init>":()V
       15:  astore_2
       16:  aload_1
       17:  invokevirtual   #6; //Method study8/DynamicDispatch$Human.sayHello:()V
       20:  aload_2
       21:  invokevirtual   #6; //Method study8/DynamicDispatch$Human.sayHello:()V
       24:  new #4; //class study8/DynamicDispatch$Woman
       27:  dup
       28:  invokespecial   #5; //Method study8/DynamicDispatch$Woman."<init>":()V
       31:  astore_1
       32:  aload_1
       33:  invokevirtual   #6; //Method study8/DynamicDispatch$Human.sayHello:()V
       36:  return
      LineNumberTable:
       line 30: 0
       line 31: 8
       line 32: 16
       line 33: 20
       line 34: 24
       line 35: 32
       line 36: 36
    
    
    }
    

    虽然17 21 语句完全一样,但是结果却不一样,原因是:从invokevirtual指令的多态查找过程开始说起,invokevirtual指令的运行时解析过程大致分为以下几个步骤:

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

    3.2.3单分派与多分派

    • 方法的接收者与方法的参数统称为方法的宗量,这个定义最早应该来源于《Java与模式》一书。根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种
    • 以Java语言的静态分派属于多分派类型
    • Java语言的动态分派属于单分派类型

    3.2.4 虚拟机动态分派的实现

    • 虚方法表(Vritual Method Table,也称为vtable,与此对应的,在invokeinterface执行时也会用到接口方法表——Inteface Method Table,简称itable),使用虚方法表索引来代替元数据查找以提高性能。

    3.3 动态类型语言支持

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

    • 虚拟机是如何调用方法的内容已经讲解完毕,从本节开始,我们来探讨虚拟机是如何执行方法中的字节码指令的
    • 整个运算过程的中间变量都以操作数栈的出栈、入栈为信息交换途径

    相关文章

      网友评论

          本文标题:第八章 虚拟机字节码执行引擎

          本文链接:https://www.haomeiwen.com/subject/ydkiottx.html