解释器先执行,省去编译的时间,立即执行。
程序运行后,编译器将代码编译成本地代码。
如果内存限制较大,可以使用解释执行节约内存,反之可以使用编译执行来提升效率。
Client Compiler和Server Compiler。用户可以指定通过-client或者-server参数。默认的情况下,HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式。
分层编译带来的好处是分工同时工作,C1获取更高的编译速度,C2获取更好的编译质量。
基于采样的热点探测(Sample Based Hot Spot Detection):
采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”。基于采样的热点探测的好处是实现简单,高效,还可以很容易获取方法调用关系(将调用堆栈展开就可以),缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。
基于计数器的热点探测(Counter Based Hot Spot Detection):
采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定阀值就认为它是热点方法。这种统计方法实现起来比较麻烦,需要为每个方法建立维护计数器。所以统计结果相对来说更加准确和严谨。
HotSpot使用第二种—基于计数器的热点探测方法,它为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter),当计数器超过阀值,触发JIT.
阀值,虚拟机参数可以通过-XX:CompileThreshold设置。
那么超过一定时间限度,始终达不到默认值,则方法计数器调用计数器减少一半。这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间称为 此方法统计的半衰周期(Counter Half Life Time)。可以通过-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数。可使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。
方法调用产生的即时编译请求还是OSR编译请求。
C1编译过程:
HIR(High Level Intermediate Representation)使用静态单分配(Static Single Assignment,SSA)的形式来代表代码值,在此之前编译器会在字节码上完成一部分基础优化,如方法内联,常量传播等优化将会在字节码被构造成HIR之前完成。
第二个阶段,一个平台相关的后端从HIR中产生低级中间代码标识(Low Level Intermediate Representation LIR),空值检查消除,范围检查消除。
第三个阶段平台相关的后端使用线性扫描算法(LinearScanRegisterAllocation)在LIR上分配寄存器,并在LIR上做窥孔(Peephole)优化,然后产生机器代码。
-XX:+PrintCompilation可以知道某个方法是否被编译过
带有%的输出说明是由回边计数器触发的OSR编译
加上另外参数-XX:+PrintInlining要求虚拟机输出方法内联信息。
对于doubleValue()方法几个例子,如果忽略语言安全检查的基本块,可以看成如下几个步骤
优化举例
1、方法内联,去除方法调用的成本(如建立栈帧等),二是为其他优化建立良好的基础。
2、冗余访问消除,假设如下示例do stuff所代表的操作不会改变b.value的值,那就可以把z=b.value替换为z=y因为上一句y=b.value已经保证了变量y与b.value是一致的,如果把b.value看做是一个表达式,那么也可以把这项优化看成是公共子表达式消除。
3、复写传播,因为如下举例中额外变量z与变量y是完全相等的,因此可以使用y来代替z。
4、无用代码消除,无用代码可能是永远不会被执行的代码也可能是完全没有意义的代码,因此被称为dead code。如下示例就是y-y是没有意义的。
原始伪代码
进行完方法内联
进行冗余访问消除优化
进行复写传播优化之后
进行无用代码消除
那么通过上面的实例简单了解了编译器的优化技术,实际上会更加复杂。
1-语言无关的经典优化技术之一:公共子表达式消除
2-语言相关的经典优化技术之一:数组范围检查消除
3-最重要的优化技术之一:方法内联
4-最前沿的优化技术之一:逃逸分析
1-语言无关的经典优化技术之一:公共子表达式消除
原理:如果一个表达式E已经计算过了,并且从先前的计算到现在E中所有变量值没改变,则E的出现就成了公共子表达式。如果这种优化仅局限于程序的基本块内,便称为局部公共子表达式消除,如果这种优化的范围涵盖了多个基本块,那就称为全局公共子表达式消除。
int d =(c*b)*12 +a +(a+b*c);
javac编译以后
但是如果是虚拟机即时编译的话,编译器检测到cb和bc是一样的,而且过程中b与c的值是不变的,则他会优化为:
int d = E*12+a+(a + E)
换可能进行代数化简优化
int d=E * 13 + a*2;
2-语言相关的经典优化技术之一:数组范围检查消除
其实就是数组下标越界,其他的还有自动装箱消除,安全点消除,消除反射等等。
3-最重要的优化技术之一:方法内联
如上面讲的简单例子
4-最前沿的优化技术之一:逃逸分析
原理:分析对象动态作用域,当一个对象在方法中被定义后,可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以再其他线程中访问的实例变量,称为线程逃逸。
如果需要,可以使用-XX:+DoEscapeAnalysis来手动开启逃逸分析,开启之后可以同过-XX:+PrintEscapeAnalysis来查看分析结果,有了逃逸分析支持,可以使用参数-XX:+EliminateAllocations来开启标量替换,使用+XX:+EliminateLocks来开启同步消除,使用参数-XX:+PrintEliminaeAllocations查看标量的替换情况。
Java语言的优化,受制于编译成本(占用用户程序运行时间),由于动态的类型安全语言,就需要动态检查耗费时间,由于自己没有virtual关键字,所以收集频率和难度也高于C/C++,Java由于内存分配都是堆上进行的,只有方法中的局部变量栈上分配,所以回收的压力稍微高些。
优点是:C/C++编译器所有优化是在编译期完成,运行期性能监控为基础的优化措施无法进行,例如调用频率预测、分支频率预测、裁剪未被选择的分支等。