执行引擎:输入字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果
栈帧是支持虚拟机进行方法调用和方法执行的数据结构。它存储在运行时数据区的虚拟机栈中。
每一个方法的从开始到完成的过程,都对应了一个栈帧的入栈和出栈的过程。
一个栈帧包含了:局部变量表,操作数栈,动态连接,方法返回地址。
局部变量表和操作数栈在编译的时候,已经可以完全确定,并且写入到了Class文件的方法表的Code属性之中。因此一个栈帧需要多大的内存,不会受到程序运行期的变量数据影响。
8.2.1 局部变量表
用来存放方法参数和方法中的局部变量。
如果是占用内存比较大的对象,在使用结束但是作用域还有其他执行比较长的语句之前,可以把它置为null,然后就可以被gc。但是不建议对所有的对象都这个处理,没有必要的地方不需要有这么多的类似代码。
同时,使用JIT编译执行的时候,赋null值的操作将会被抹除。
局部变量不会被赋予初始值,所以必须初始化才能使用。
8.2.2 操作数栈
一个后进先出的栈。
我理解的,局部变量用于存储,操作数用于计算。比如执行一个加法操作,需要将两个数值压入操作数栈顶,调用其他方法的时候,可以通过操作数栈来传递参数。
8.2.3 动态链接
每个方法都包含一个指向运行时常量池中的方法引用。持有这个引用(这里应该是指符号引用)就可以支持在方法调用的过程中动态连接。
8.2.4 方法返回地址
方法只有2中退出方式,正常情况下,遇到return指令退出。还有就是异常退出。
正常情况:一般情况下,栈帧会保存 在程序计数器中的调用者的地址。虚拟机通过这个方式,执行方法调用者的地址,
然后把返回值压入调用者中的操作数栈。
异常情况:方法不会返回任何值,返回地址有异常表来确定,栈帧一般不存储信息。
8.2.5 附加信息
方法调用不等同于方法执行,唯一任务就是确定被调用方法的版本
8.3.1 方法调用
方法调用阶段不是执行该方法,而仅仅时确认要调用那个方法。class文件在编译阶段没有连接这一过程,、
所以动态连接这个在C++就已经有的技术,在java运用到了一个新的高度。所有的函数(除了私有方法,构造方法 & 静态方法,下同),理论上
都可以时C++里面的虚函数。所以所有的函数都需要通过动态绑定来确定“明确”的函数实体。
解析
所有方法调用的目标方法都是常量池中的符号引用。在类的加载解析阶段,会将一部分目标方法转化为直接引用。(可以理解为具体方法的直接地址)
可以转化的方法,主要为静态方法 & 私有方法。
Java虚拟机提供5中方法调用命令:
invokestatic:调用静态方法
invokespecial:调用构造器,私有方法和父类方法
invokevirtual:调用虚方法
invokeinterface:调用接口方法
invokedynamic:现在运行时动态解析出该方法,然后执行。
invokestatic & invokespecial 对应的方法,都是在加载解析后,可以直接确定的。所以这些方法为非虚方法。
java规定 final修饰的是一种非虚方法。
8.3.2 分派
静态分派
静态类型(外观类型):变量本身的静态类型不会被改变,最终的静态类型是在编译期可知的
实际类型:变化结果在运行期才确定
使用哪个版本的重载,完全取决于传入参数的数量和数据类型;虚拟机(准确的说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据;并且静态类型是编译期可知的,因此,在编译阶段,javac编译器会根据参数的静态类型决定使用哪个版本的重载
所有依赖静态类型来定位方法执行版本的分派称为静态分派
静态分派的典型应用就是方法重载
动态分派
重写
invokevirtual的运行时解析过程:
找到操作数栈顶的第一个元素指向的对象的实际类型,记作C
如果类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束,如果不存在,则返回java.lang.IllegalAccessError异常
否则,按照继承关系从下往上依次对C的各个父类进行第二步的查搜索和验证过程
如果始终没有找到,则抛出java.lang.AbstractMethodError异常
运行期根据实际类型确定方法版本的分派称为动态分派
单分派和多分派
方法的接受者方法的参数统称为方法的宗量,根据分派基于多少种宗量,可以将分派划分为单分派和多分派
单分派是根据一个宗量对目标方法进行选择
多分派是根据多于一个宗量对目标方法进行分派
静态分派属于多分派
动态分派属于单分派
虚拟机动态分派的实现
动态类型语言支持
8.4.1解释执行
javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,在遍历语法树生成线性的字节码指令流的过程
一部分在虚拟机之外进行,而解释器是在虚拟机内部,所以Java程序的编译是半独立的实现
8.4.2基于栈的指令集和基于寄存器的指令集
基于栈的指令集主要优点是可移植性、代码更加紧凑、编译实现更简单;缺点是执行速度相对慢点
8.4.3基于栈的解释器执行过程