JAVA最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁,就会把这些代码认定为“热点代码”而将它们编译成本地机器码
HOTSPOT和J9都是解释器和编译器并存,保留解释器的原因是,加快启动时间,立即执行,当运行环境中内存资源限制较大时,解释器可以节约内存,解释器还可以作为激进优化的编译器的“逃生门”(称为逆优化Deoptimization)
HOTSPOT内置了两个即时编译器,client compiler和server compiler,称为C1,C2,默认是采用解释器与其中一个编译器直接配合的方式工作。HOTSPOT会根据自身版本和宿主机器的性能自动选择运行模式,用户也可以使用-client或-server来决。
这种解释器编译器搭配的方式成为混合模式,用户还可以使用-Xint强制虚拟机使用“解释模式”
也可以使用-Xcomp强制“编译模式”
被编译的触发条件:
- 被多次调用的方法
- 被多次执行的循环体(栈上替换)OSR On Stack Replacement
判断是否是热点代码的行为成为热点探测:hot spot detection,主要的热点探测方式主要有两种:
- 基于采样的热点探测,JVM会周期性检查各个线程的栈顶,如果某个方法经常出现在栈顶,那就认定为热点方法。简单高效,精度不够。
- 基于计数器的热点探测,统计方法执行次数。(HOTSPOT使用这种方式)
HOTSPOT有两个计数器:方法调用计数器和回边计数器
方法调用计数器client默认1500次,server默认10000次,可以通过参数
-XX:CompileThreshold来设定。调用方法时,会先判断是否存在编译过的版本,如果有则调用该版本,否则计数器加1,
然后看方法调用计数器和回边计数器之和是否超过方法调用计数器的阈值。超过,则提交编译请求
方法调用计数器并不是统计方法调用绝对次数,而是一个
相对执行频率,超过一定时间,
如果方法调用次数不足以让它提交给编译器,则计数器就会被减少一半,这种现象称为热度衰减(Counter Decay),
进行热度衰减的动作是在垃圾回收时顺便进行的,而这段时间就被称为半衰周期(Counter Half Life Time)可用-XX:-UseCounterDecay来关闭热度衰减,用-XX:CounterHalfLifeTime来设置半衰时间。
回边计数器用于统计方法中循环体的执行次数。字节码遇到控制流向后跳转 的指令成为回边。回边的控制参数有
-XX:BackEdgeThreshold,-XX:OnStackReplacePercentage.
编译是后台进行的,可以通过-XX:-BackgroundCompilation来禁止后台编译