前面介绍了C1和C22中编译方式,在JAVA中还有一种特殊的方式OSR编译。它与前2种不同的地方在于它替换的是方法体得入口,而C1和C2替换的是方法的调用入口。什么意思呢?个人的理解是C1和C2只是编译了调用方法的那段代码、方法的内容可能仍是解释执行而OSR则是编译了方法的整段内容。
默认情况下具体开启的是哪种模式呢?在JAVA中默认在32位的系统下都是只开启C1模式,在64位机器下如果是双核且超过2G内存,则会开启C2模式,当然你也可以在启动的时候来强制指定模式。
说了以上几种模式大家也都感觉到了编译执行比解释执行快很多,但为什么JAVA不在启动时就把所有代码都编译成机器码呢?
1.在前面提到过一个概念程序C2收集运行时间越长优化的代码就越佳,这很好理解想一些分支优化、逃逸分析等等都只能在程序运行一段时间以后才会知道。
2.还有解释执行省内存。
3.启动上解释执行快。
但JAVA是如何知道什么时候要把一段代码编译为机器码得呢?
首先介绍2个计数器:
调用计数器,方法的调用次数。
回遍计数器,循化体代码的执行次数。
在介绍2个对应的阀值:
调用计数器----CompileThreshold
:该值定义方法被调用多少次后就编译为机器码。在JAVA中有个默认值c1为1500次,c2为10000次。可在启动时添加-XX:CompileThreshold=10000来设置该值
回遍计数器----OnStackReplacePercentage:循环执行多少次(有计算规则)后是否触发OSR编译,C1为933,C2为140。计算规则分别是C1为CompileThreshold
*(OnStackReplacePercentage/100),C2为(CompileThreshold*
(
OnStackReplacePercentage-InterpreterProfilePercentage(默认值为33))/100,
所以C1默认情况下市(1500*
(933/100))=13995,C2为(10000*(140-33))/100=10700就会分别触发OSR调用。还有在如果触发了OSR编译的话就会把该循环相关的方法的调用计数器置为CompileThreshold的值,并把回边计数器置为CompileThreshold/2,第2步为了防止再次马上再次触发OSR编译第一步则是为了让该方法再下一次调用的时候编译为机器码。
看一段代码大家就能更明白了:
类为Test
public static void main(String[] args){
Test test = new Test();
for(int
i=0;i<10;i++){
test.cycle();
}
}
public void cycle(){
for(int i=0;
i<10700;i++){
//do sth
}
}
在调用循环方法体第一次的时候看到自己循环了10700当循环完成后cycle方法的CompileThreshold
=1,回边计数器则为10700,触发了OSR编译这时候CompileThreshold
=10000,回遍计数器为5000。当main中的循环执行第2次得时候由于CompileThreshold
=10000这时候也出发了C2编译方法入口也会被编译成机器码当然第一次还是继续解释执行不等待然后进入循环体检查前面的该循化体的OSR编译是否完成完成的话就执行OSR编译后的机器码方法体,没的话就继续解释执行不等待如此循环。
到这里编译执行的内容作了大致的介绍,明天开始介绍反射执行。