注:主要参考自《分布式java应用:基础与实践》《深入理解Java虚拟机(第二版)》
1、两种执行方式:
- 解释执行(运行期解释字节码并执行)
- 强制使用该模式:-Xint
- 编译为机器码执行(将字节码编译为机器码并执行,这个编译过程发生在运行期,称为JIT编译)
- 强制使用该模式:-Xcomp,下面是两种编译模式
- client(即C1):只做少量性能开销比高的优化,占用内存少,适用于桌面程序。
- server(即C2):进行了大量优化,占用内存多,适用于服务端程序。会收集大量的运行时信息。
注意:
- 32为机器默认选择C1,可在启动时添加-client或-server来指定,64位机器若CPU>2且物理内存>2G则默认为C2,否则为C1
- Hotspot JVM执行代码的机制:对在执行过程中执行频率高的代码进行编译,对执行频率不高的代码继续解释执行
查看当前机器默认是client模式还是server模式,使用:"java -version"命令,如下
其中,mixed mode表示"解释执行+编译执行"的混合模式
2、解释执行
查看 第三章 类文件结构与javap的使用 中的inc()方法的执行
或者查看《深入了解java虚拟机(第二版)》P272-P275
3、编译执行
- 编译的对象
- 方法
- 方法中的循环体
- OSR编译:编译整段代码,但是只有循环体部分会执行机器码,其他部分还是解释执行
- 触发条件(执行频率大于多少)
- 方法调用计数器:方法被调用的次数
- client:1500 server:10000
- 该阈值可通过-XX:CompileThreshold来指定
- 这里"方法调用的次数"是指一段时间(半衰周期)内的调用次数,如果半衰周期内,该次数没有达到阈值,则该次数减半。
- -XX:-UseCounterDecay 关闭上述机制,即半衰周期的无穷大
- -XX:CounterHalfLifeTime 半衰周期
- 回边计数器:循环体内循环代码的执行次数(即for中代码的循环的次数)
- client:13995 server:10700
- 该阈值可通过-XX:OnStackReplacePercent(注意该OSRP只是一个计算回边计数阈值的中间值),回边计数阈值
- client:CompileThreshold*OSRP/100
- server:CompileThreshold*(OSRP-InterPreterProfilePercentage)/100
- -XX:OnStackReplacePercent:140 InterPreterProfilePercentage:33
- 方法调用计数器:方法被调用的次数
- 方法编译执行
- 解释器调用方法时,检查是否有已经存在的编译版本,如果有,执行机器码,如果没有,方法调用计数器+1,然后判断方法调用计数器是否超过阈值,若超过,进行编译,后台线程进行编译,前台线程继续解释执行(即不会阻塞),直到下一次调用方法时,如果编译好了,就直接执行机器码,如果没编译好,就解释执行。
- 循环体编译执行
- 解释器执行到循环体时,检查是否有已经存在的编译版本,如果有,执行机器码,如果没有,回边计数器+1,然后判断回边计数器是否超过阈值,若超过,进行编译,后台线程进行编译,前台线程继续解释执行(即不会阻塞),直到下一次执行到循环体时,如果编译好了,就直接执行机器码,如果没编译好,就解释执行。
4、C1优化
说明:关于全部的优化技术列表,查看《深入理解java虚拟机(第二版)》P346-P347
只做少量性能开销比高的优化,占用内存少,主要的优化包括:
- 方法内联
- 冗余消除
- 复写传播
- 消除无用代码
- 类型继承关系分析(CHA,辅助)
- 去虚拟化
4.1、方法内联、冗余消除、复写传播、消除无用代码
4.1.1、方法内联
方法内联含义:假设方法A调用了方法B,把B的指令直接植入到A中。
static class B{ int value; final int get() { return value; } } public void foo() { y = b.get(); //do something z = b.get(); sum = y + z; }