概述
- 当虚拟机发现某个方法或代码运行特别频繁时,就会把这些代码认定为"热点代码"。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成本地平台相关的机器码,完成这个任务的编译器称为即时编辑器(JIT编译器)
HotSpot虚拟机内部的即时编译器
一、解释器与编译器
-
当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。
-
编译器的作用,把代码编译成本地代码,可以获得更高的执行效率。
-
解释器可以作为编译器激进优化时的一个"逃生门"。
-
在整个虚拟机执行架构中,解释器与编译器配合工作。
-
HotSpot虚拟机中内置了两个编译器。Client Compiler 和Senner Compiler称为C1编译器和C2编译器。
-
解释器与编译器搭配使用的方式咋虚拟机中称为"混合模式"。 二、编译对象与触发条件
-
被即时编译的"热点代码"有两类:被多次调用的方法、被多次执行的循环体。
-
两类热点代码编译器都会以整个方法作为编译对象。
-
前类使用JIT编译,后类使用OSR编译器。
-
判断一段代码是不是热点代码,这样的行为称为热点探测。
-
主要的热点探测判断方法有两种:基于采样的热点探测、基于计数器的热点探测。
i. 采用这种方法的虚拟机会周期性的检查各个现场的栈顶,如果发现某个方法经常出现在栈顶,那这个方法就是"热点方法"。
ii. 采用这种方法的虚拟机会为每个方法建立计数器,统计方法执行次数,如果执行次数超过一定的阈值,认为他是热点方法。
-
HotSpot虚拟机采用计数器的热点探测方法,因此为每个方法准备了两类计数器:方法调用计数器和回边计数器。
-
当超过一定时间限度,方法调用次数,仍然不足以让它提交给即时编辑器,编译这个方法的调用计数器,会被减少一半,这个过程称为方法调用计数器的热度衰减,这段时间称为方法的半衰周期。
-
回边计数器,他的作用是统计一个方法中,循环体的代码执行次数。 三、编译过程
-
Server Compiler它是一个简单的快速的段式编译器。
-
编译的三个阶段:
i. 第一个阶段,一个平台的独立前端将字节码构造成一种高级中间代码表达式HIR。HIR使用静态但分配的形势来代表代码值。
ii. 第二个阶段,一个平台相关的后端从HIR中产生低级中间代码表示(LIR)。
iii. 最后一个阶段是在平台相关的后端,使用线性扫描算法,在LIR上分配寄存器,然后产生机器代码。
编译优化技术
一、公共子表达式消除
-
如果一个表达式E已经计算过了,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么没有必要花时间在对E进行计算,直接使用,称为公共子表达式消除。 二、数组边界检查消除
-
如果编译器只要通过数据流分析就可以判断循环变量的取值范围,那么整个循环中就可以把数组上下界检查消除。 三、方法内联
-
方法内联看似简单,不过是把目标方法的代码"复制"到发起调用方法中。
-
编译器在进行内联时,如果非虚方法,那么直接进行内联。
-
如果遇到虚方法,则会查询此方法在当前程序是否有多个版本,可供选择,如果只有一个版本,那么可以进行内联,属于激进优化,需要预留一个"逃生门"称为保守内联。
-
如果查询出来的结果有多个版本目标方法可供选择,编译器会使用内联缓存来完成方法内联,这是一个简历在目标方法正常入口之前的缓存。大致原理是:在未发生方法调用之前,内联缓存状态为空,当第一次调用发生后,缓存记录方法接受者的版本信息,如果以后进来的每次调用的方法版本不一致,取消内联。 四、逃逸分析
-
当对象作为调用参数传到其他方法中,称为方法逃逸。
-
对象可能被外部线程访问到,称为线程逃逸。
-
对象如果不会逃逸到方法到线程上去可以进行的优化。
i.栈上分配:如果确定一个对象不会逃逸出方法之处,那把这个对象在栈上分配内存。会随栈帧出栈而销毁。
ii.同步消除:线程同步本身是一个比较耗时的过程,如果逃逸分析确定一个变量,不会逃逸出线程,那么这么变量实施的同步可以消除掉。
iii.标量替换:标量是指一个数据无法分解成更小的数据表示了,如果一个额数据可以继续分解,称为聚合量。如果把一个对象拆散,将替换成标量,称为标量替换。
我是菜鸟,希望大家多多留言讨论~谢谢!
我的笔记是看完深入理解java虚拟机的体会,是本好书,推荐给大家.