八、JVM(HotSpot)虚拟机字节码执行引擎

注:本博文主要是基于JDK1.7会适当加入1.8内容。

物理机执行引擎:直接建立在处理器、硬件、指令集和操作系统层面。
虚拟机执行引擎:由虚拟机自定义实现,自行制定指令集与引擎体系结构,能够执行不被硬件执行的指令集合。(符号引用—-直接引用)

1、运行时栈帧结构

定义:用于支持虚拟机进行方法调用和方法执行的数据结构,虚拟机运行时数据区中的虚拟机栈的栈元素。存储了局部变量表、操作数栈、动态链接和方法返回地址等信息。
栈帧中需要多大的局部变量表、多深的操作数栈由虚拟机确定,并写入方法表中的Code属性中。一个线程中方法调用链可能很长,很多方法都处于运行状态,对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称之为当前栈帧,与之相关联的方法称之为当前方法

局部变量表

一组变量存储空间,用于存放方法参数和方法内定义的局部变量。Java编译器为Class文件时,方法内的Code属性的max_locals数据项确定了该方法局部变量表的容量以变量槽(Slot)为最小单位。

操作数栈

后入先出栈,它的最大深度由虚拟机编译时候写入Code属性中的max_stacks数据项中。32为数据类型占栈容量为1,64位数据类型占栈容量为2.

动态链接

每个栈帧都包含一个纸箱运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持该方法调用过程中的动态调用。Class文件的常量池中存在大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用为参数,这些符号引用有的在类加载阶段或者第一次使用的时候就转化为直接引用,称之为静态解析;由的将在每一次运行期间转化为直接引用,称之为动态连接

方法返回地址

执行引擎遇到任意一个方法返回的字节码指令,可能返回值传递回给调用者,是否有返回值以及返回值的类型根据遇到何种方法返回指令决定,这种退出方法为正常完成出口;方法执行过程中遇到异常,并且这个异常在方法内得到了处理,无论是Java虚拟机内部产生的异常还是代码中使用athrow字节码指令产生的异常,只要方法内异常表没有搜索到匹配的异常处理器就会导致方法退出,这种退出方法为异常完成出口

附加信息

虚拟机增加一些规范里没有描述的信息到栈帧中,例如调试相关信息,这取决于具体的虚拟机实现。

2、方法调用

定义:方法调用不同于方法执行,唯一的任务是确定被调用的方法的版本,即调用的是哪个方法,不涉及方法内部的具体运行过程。

解析

类加载解析阶段,会将其中一部分符号引用转化为直接引用,这种解析成功的前提:方法程序真正运行之前就有一个可确定的版本,并且这个方法的调用版本在执行期是不可变的。这类方法调用称为解析。
主要包括:静态方法和私有方法两大类。与之相对应的是Java虚拟机提供了5条方法调用字节码指令,分别是:
1)invokestatic:调用静态方法
2)invokespecial:调用实例构造方法< init >方法、私有方法和父类方法
3)invokevirtual:滴啊用虚方法
4)invokeinterface:调用接口方法,会在运行时再确定一个实现该方法的对象
5)invokedynamic:先运行时动态解析调用点限定符引用的方法,再执行该方法

注:虽然final方法是使用invokevirtual来调用的,但由于它无法被覆盖,没有其他版本,所以无须对方法进行多态选择,或者说多态选择的结果是唯一的,Java语言规范中明确说明final方法是一种非虚方法。这些指令都可以通过javap -c *.class反编译查看。

分派

1)静态分派—–>静态类型、实际类型、方法重载
2)动态分派—–>方法重写
3)单分派和多分派
宗量:方法的接收者和方法的参数。
单分派:根据一个宗量对目标方法进行选择。
动态单分派:方法类型和静态类型确定,唯一不确定的是实际类型。
多分派:根于多于一个宗量对目标方法进行选择。
静态多分派:静态类型,方法参数。
4)虚拟机动态分派的实现
方法:方法表、内联缓存、基于类型继承关系分析技术的守护内联。

动态类型语言支持:invokedynamic指令,变量无类型,变量值有类型

1)动态类型语言
动态语言和静态语言对比:
静态类型语言在编译器确定类型,最显著的好处是编译器可以提供严谨的类型检查,于类型相关的问题能在编码时候及时发现,利于稳定性及代码达到更大规模;动态类型语言在运行期确定类型,为开发者提供更大的灵活性。

2)JDK1.7与动态类型:invokedynamic指令与java.lang.invoke包

java.lang.invoke包
目的:在之前单纯依靠符号引用来确定调用的目标方法这种方法外,提供一种新的动态确定目标方法的机制,称之为MethodHandle。
MethodType和Reflection区别:
A、本质上来说,Reflection和MethodType都是模拟方法的调用,但Reflection是模拟代码层面的调用,而MethodType模拟字节码层面的调用。MethodTypes.lookup中的3个方法:findStatic()、findVirtual()、findSpecial()对应invokestatic、invokevirtual&invokeinterface、invokespecial字节码指令的执行权限行为。
B、Reflection的java.lang.reflect.Method对象远比MethodType机制中的java.lang.invoke.MethodHandle对象包含的信息多。前者是方法在Java一端的全面映像,包含了方法的签名、描述符以及方法属性表中的各种属性的Java端表示方法,还包含权限等运行期信息。而MethodHandle仅仅包含与该方法相关的信息。Reflection是重量级的,MethodHandle是轻量级的。
C、MethodHandle是字节码的方法指令调用模拟,虚拟机在这个层面的各种优化,MethodHandle也可以采用类似思路进行。最关键的一点是Reflection是Java语言层面的,MethodHandle是针对Java虚拟机也就是说可以针对到Java虚拟机上的非Java语言。

invokedynamic指令
invokedynamic指令的位置称作动态调用点,CONSTANT_InvokeDynamic_info常量。从这个常量中可以获取三项信息:引导方法(Bootstrap Method)、方法类型(MethodType)和名称。

3)掌控方法分派规则

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

解释执行

下图为编译原理编译过程。Java语言中,javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性字节码指令流的过程。因为这一部分动作在虚拟机之外进行的,而解释器在虚拟机内部,所以Java程序的编译就是半独立的实现。
这里写图片描述

基于栈的指令集与基于寄存器的指令集

基于栈的指令集与基于寄存器的指令集区别:
基于栈的指令集主要优点可移植,寄存器是��硬件直接提供,程序使用寄存器的指令集不可避免要受到硬件的约束;
基于栈的指令集代码相对更加紧凑(字节码中每个字节对应一条指令,而多地址指令集中还需要存放参数);
基于栈的指令集编译器实现更加简单(不需要考虑空间分配问题,所需空间在栈上操作)等;
基于栈的指令集主要问题是速度相对缓慢,出栈,进栈产生大量的指令集,栈实现在内存中,频繁的栈访问意味着频繁的内存访问,尽管虚拟机采用栈顶缓存技术,最常用的操作映射到寄存器中避免直接内存访问,但也只是优化手段并非解决问题的本质方法。

基于栈的解释器执行过程

猜你喜欢

转载自blog.csdn.net/zhangwei408089826/article/details/81779143