一、虚拟机字节码执行引擎
1.虚拟机执行引擎由自己实现,所以可以自行制定指令集与执行引擎的体系结构,并且可以执行那些不被硬件直接支持的指令集格式。
2.执行引擎
- 编译执行:通过JIT编译器产生本地代码执行
- 解释执行:通过解释器解释执行
二、运行时栈帧结构
- 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
- 对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧。
1.局部变量表
- 编译时,在方法的Code属性的max_locals确定需要分配的局部变量表的最大容量
- 对于实例方法,局部变量表中的0位索引的Slot默认记录方法所属对象实例的引用,即this指针
- 根据变量作用域,Slot可以被复用,可能对垃圾回收产生影响,如果对变量赋null,在JIT编译中会被清除,最优雅的解决方法是以恰当的变量作用域来控制变量的回收时间
2.操作数栈
- LIFO栈
- 最大深度为编译时写入方法表中Code属性的max_stacks
- 可能会有不同栈间的数据共享
3.动态连接
- 每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,用来支持动态连接
- 字节码中的方法调用指令:以常量池中指向方法的符号引用为参数
- 符号引用解析:一部分发生在类加载或者第一次使用,即静态解析,动态解析发生在每一次运行期
4.方法返回地址
- 正常完成出口
- 异常完成出口
5.附加信息
- 在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息。
三、方法调用
- 目的:确定调用方法的版本
- 一切方法调用在Class文件里存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于直接引用)
1.解析
- 类加载的解析阶段,会把其中一部分符号引用转化为直接引用,前提:方法在程序运行之前只有一个可确定的调用版本,并且这个调用版本在运行期不可变
- 编译器可知,运行期不可变:静态方法与私有方法,
- 五条方法调用字节码指令
1)invokestatic:调用静态方法
2)invokespecial:调用实例构造器方法,私有方法和父类方法。
3)invokevirtual:调用虚方法。
4)invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
5)invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
- 只要能被invokestatic与invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,符合这个条件的有静态方法,私有方法,实例构造器和父类方法四类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以统称为非虚方法,与之相反,其它方法就称为虚方法(除去final方法)。
- final方法虽然由invokevirtual调用,但是由于它无法被覆盖,没有其他版本,所以也是非虚方法
2.分派
- 静态分派:依赖静态类型来定位方法执行版本的分派动作称为静态分派,典型应用为方法重载
1)Shape s = new Circle(),Shape称为静态类型,Circle称为实际类型
- 动态分派:依赖动态类型执行方法分派,典型应用为重写
1)动态分派时,会根据当前栈帧的局部变量表,进行挨个遍历匹配接受者(即调用者),由于invokevirtual指令执行的第一步就是在运行期确定接受者的实际类型,这个过程就是Java语言重写的本质
- 单分派与多分派
1)方法的接受者与方法的参数称为方法的宗量。
2)单分派是根据一个宗亮对目标方法进行选择,多分派是根据多个宗量
3)Java语言:静态多分派、动态单分派
4)动态分派,优化手段:虚方法表,若方法没被重写,则地址入口与父类相同方法地址一致
3.动态语言支持
- 反射与invokedynamic指令
四、基于栈的字节码解释执行引擎
1.解释执行
- Java 语言中, Javac 编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程
- 解释器在JVM内部,所以Java程序的编译是半独立的实现
- 中间过程:解释执行
- 下面过程:传统编译原理程序代码到目标机器代码的生成过程
2.基于栈指令集与寄存器指令集
- 基于栈的指令集主要的优点就是可移植,寄存器由硬件直接提供,程序直接依赖这些硬件寄存器则不可避免地要受到硬件的约束。
- 栈架构指令集的主要缺点是执行速度相对来说会稍慢一些。
- 虽然栈架构指令集的代码非常紧凑,但是完成相同功能所需的指令数量一般会比寄存器架构多,因为出栈、入栈操作本身就产生了相当多的指令数量,,栈实现在内存之中,频繁的栈访问也就意味着频繁的内存访问,相对于处理器来说,内存始终是执行速度的瓶颈。
五、类加载器
1.Tomcat
2.OSGI:运行时生成类加载体系结构
3.字节码生成与动态代理技术
4.Restrotranslator:Java逆向移植工具
- 基本原理是将新特性在编译时进行替换,如包装对象,更换为Integer.valueOf()
参考:
https://gavinzhang1.gitbooks.io/java-jvm-us/content/xu_ni_ji_zi_jie_ma_zhi_xing_yin_qing.html