执行引擎
- Java虚拟机核心组成部分
- 物理机的执行引擎直接建立在处理器、缓存、指令集和系统层面上
- 虚拟机的执行引擎是由软件自行实现的,不受物理条件限制地定制指令集、执行引擎的结构体系,能够执行不被硬件直接支持的指令集格式
理解
字节码
- JVM主要任务是装载字节码到其内部
- 字节码不能够直接运行在操作系统上,字节码指令并非等价于本地机器指令、内部包含的仅仅是能够被JVM识别的字节码指令、符号表、其他辅助信息
- 执行引擎将字节码指令解释或编译为对应平台dd阿本地机器指令才能运行程序(执行引擎将高级语言翻译为机器指令)
- 中间状态的二进制文件,比机器码抽象,直译后编程机器码
- 编译器将源码转换为字节码,特定平台的虚拟机将字节码转译为可以直接执行的指令
工作流程
- 执行引擎在执行的过程需要执行什么样的字节码指令依赖于PC寄存器
- 执行一项指令后,PC寄存器更新下一条指令
- 方法的执行过程中,执行引擎可能通过存储在局部变量表的对象引用准确定位到存储在Java堆中的对象实例信息、通过对象头中的元数据指针定位到目标对象类型的信息
- 输入的字节码二进制流,处理过程是字节码解析执行的等效过程,输出的是执行结果
Java代码编译、执行过程
解释器
- Java启动时会根据预定义的规范对字节码采用逐行解释的方式执行(字节码文件的内容翻译为对应平台的本地机器指令执行)
- JIT(Just in time Compiler):虚拟机将代码直接编译成本地机器平台相关的机器语言
- 为满足Java程序跨平台性,因此避免采用静态编译的方式直接生成本地机器指令
- 采用实现解释器在运行时采用逐行解释字节码执行程序
- 当一条字节码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码指令执行解释操作
解释器类型
- 字节码解释器在执行时通过采用纯软件模拟字节码的执行,效率低
- 模板解释器将每一条字节码和一个模板函数相关联,模板函数直接产生这条字节码执行时的机器码,提高性能
- HotSpot VM中,解释器主要由Interpreter模块(实现了解释器核心功能)、Code模块(管理HotSpot VM在运行时生成的本地机器指令)
JIT编译器
- 为提高效率,将方法编译为机器码后再执行编译执行
- JVM平台支持一种叫即时编译的技术,即时编译的目的是避免函数被解释执行,而是将整个函数编译为机器码,每次函数执行时,只执行编译后的机器码
概念介绍
- 前端编译器:java文件转变成class文件
- 后端编译器:字节码转变为机器码的过程
- 静态提前编译器:直接将java文件编译为本地代码的过程
半编译半解释型语言
- Java1.0定位是解释执行,后来发展出可以直接生成本地代码的编译器,现在Java在执行代码时,通常会会将解释执行、编译执行二者结合起来
- 程序启动后,解释器可以马上发挥作用,省去了编译的时间、立即执行
- 编译器把编译成本地代码需要执行一定的执行执行,编译为本地代码后执行效率高
- 对于服务器,启动时间并非重点(服务器启动,准备服务较长,但启动完毕长时间提供服务),采用解释器、即时编译器并存的架构来换取平衡点
- 虚拟机启动,解释器可以首先发挥作用,不必等待即时编译器全部编译再完成,这样可以省去许多不必要的编译时间
- 随着时间推移,根据热点探测功能,将有价值的字节码编译为本地机器指令,提高效率
指令
- 机器码是0、1组成的二进制,可读性差,人们发明了指令(如mov)
- 不同硬件、执行同一个操作,对应的字节码可能不同,不同硬件平台的同一个指令对应的机器码可能不同
热点代码探测
- 热点代码:多次被调用的方法、方法内部循环次数多的循环体
- 编译方式发生在方法的执行过程,因此被称为栈上替换(On Stack Replacement)
- HotSport VM的热点探测方式基于计数器
- 方法调用计数器:统计方法调用次数(阈值在1500,Server模式是10000次)
- 回边计数器:统计循环体的循环次数(字节码遇到控制流向后跳转的指令成为“回边”)
- 方法被调用时,先判断方法是否被JIT编译过,如果存在,就优先使用已编译的版本,如果不存在,计数器加一,然后判断方法调用计数器与回边计数器之和是否超过阈值,已超过阈值,会向即时编译器提交方法的代码编译请求
热度衰减
- 当超过一定时间限度,如果方法的调用次数仍不足让它提交给即时编译器编译,调用计数器将减半,这段时间成为此方法统计的半衰周期
- 热度衰减是在虚拟机垃圾回收时进行的,
- 关闭参数:-XX:-useCounterDecay
- 半衰周期参数:-XX:CounterHalfLifetime(单位秒)
相关参数
- 完全采用解释器模式:-Xint
- 完全采用即时编译器:-Xcomp
- 混合模式:-Xmixed
- 指定运行Client模式:-Client,C1编译器会对字节码简单、可靠的优化,耗时短,更快的编译速度
- 指定运行Server模式:-Server,C2进行耗时较长的优化、激进优化,执行效率高
分层编译
- 程序解释执行(不开启性能监控)可以触发C1编译,将字节码编译为机器码,可以简化优化,也可以加上性能监控,C2编译会根据性能监控信息进行激进优化
- 一旦开发人员显式指定了Server模式,默认开启分层编译策略,C1、C2相互协作
优化策略
- C1优化
- 方法内联:将引用的函数代码编译到引用点处,减少栈帧的生成、减少参数传递以及跳转过程
- 去虚拟化:对唯一的实现类进行内联
- 冗余消除:把不会执行的代码折叠掉
- C2优化
- 标量替换:标量代替聚合对象的属性值
- 栈上分配:对未逃逸的对象分配到栈上
- 同步消除:清除同步操作
其他
- Graal编译器编译效果追平了C2编译器
- AOP编译器在程序运行前就将字节码转换为机器码的过程,减少Java应用带来“第一次运行慢”,破坏了“一次编译,到处运行”,必须针对不同硬件、OS编译发行包,降低了Java链接过程的动态性,加载的代码编译期就必须全部已知