执行引擎是java虚拟机核心的组成部分之一
虚拟机是一个相对于物理机的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎则是由件自行实现的,因此可以不受物理条件制约地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集格式。
解释器和JIT编译器
解释器:当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。
JIT(Just In Time Compiler)编译器:就是虚拟机将源代码编译成和本地机器平台相关的机器语言。
JIT用来将会重复使用的代码编译成本地代码来提高效率。但是编译过程也会花费时间,为了在编译过程中程序也能为我们做出响应,使用解释器来弥补。
JIT即时编译器后的代码会放在方法区CodeCache处
热点代码及探测方式
一个被调用多次的方式,或者是一个方法体内部循环次数较多的循环体都可以被称之为“热点代码”。可以通过JIT编译器编译为本地机器指令,由于这种编译方式发生在方法的执行过程中,因此也被称为栈上替换,OSR(On Stack Replacement)编译
HotSpot使用的热点探测方式是基于计数器的热点探测。
HtpSpot会为每一个方法都建立2个不同类型的计数器,分别是方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。
- 方法调用计数器用于统计方法的调用次数
- 回边计数器则用于统计循环体执行的循环次数
方法调用计数器,默认阈值在Client模式下是1500次,在Server模式下是10000次,超过这个阈值,就会触发JIT编译。
可通过-XX:CompileThreshold
来设定阈值
热度衰减
方法调用计数器统计的不是方法被调用的绝对次数,而是一段时间内的执行频率。如果是绝对次数,那么只要程序运行时间足够长,总是会有达到阈值的可能性。在一定的时间内,如果方法的统计次数无法达到阈值,那么就将这个方法的调用计数器的值减少一半,这个过程称为热度衰减。
可以使用-XX:-UseCounterDecay
来关闭热度衰减。
通过-XX:CounterHanfLifeTime
来设置半衰期的时间,单位为秒
模式设置
-Xint
:纯解释器模式-Xcomp
:纯编译器模式-Xmixed
:混合模式。默认
JIT分类
HotSpot中有两个JIT编译器,分别为Client Compiler和Server Compiler,我们一般简称为C1和C2编译器。
-client
:指定java虚拟机运行在Client模式系,使用C1编译器- C1编译器会对字节码进行简单和可靠的优化,耗时短。以达到更快的编译速度
-server
:指定java虚拟机运行在server模式下,使用C2编译器。- C2进行耗时较长的优化,以及激进优化。但优化代码执行效率更高。
C1和C2不同的优化策略
C1编译器主要有方法内联,去虚拟化,冗余消除
- 方法内联:将引用的函数代码编译到引用点,这样可以减少 栈帧的生成,减少参数传递以及跳转过程。
- 去虚拟化:对唯一的实现类进行内联
- 冗余消除:在运行期间把一些不会执行的代码折叠掉
C2的优化主要是在全局层面,逃逸分析是优化的基础。
- 标量替换:用标量值代替聚合对象的属性值
- 栈上分配:对于未逃逸对象分配对象在栈而不是堆
- 同步消除:清除同步操作,通常值synchronized
在java7之后,一旦开发人员显示指定命令-server
,默认会开启分层编译策略,由C1编译器和C2编译器相互协作共同来执行编译任务。