解释器: 每次读取一代码,将代码转换成对应JVM执行的指令,然后调用JVM指令执行。
即时编译:将源代码直接生成符合本地物理机可识别的机器语言。
解释器对同样的代码每次都要重新翻译成JVM指令,效率低下。即时编译器的好处在于可以对代码进行深度优化,同时提高效率。
即时编译(JIT)
JIT是just in time的缩写,也就是即时编译编译器。
通常通过javac将程序源代码编译成class文件,JVM通过解释class文件内部的字节码,将其翻译成对应的机器指令,逐条读入,逐条解释翻译。显然,经过解释执行,其执行速度肯定比可执行的二进制字节码慢。为了提高JVM的执行速度,引入了JIT技术。
1. 即时编译过程图
2. 客户的模式或服务器模式
JIT编译器在运行程序时有两种编译模式可以选择,并且其会在运行时决定使用哪一种以达到最优性能。当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器,而-server 模式启动的虚拟机采用相对重量级代号为 C2 的编译器。C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。
可以通过java -version查询当前系统使用的模式:
- Server VM说明当前系统模式的是server模式(也可通过-client或者-server指定)
- mixed mode说明当前JVM使用解释器与即时编译混合搭配使用。参数-Xint强制虚拟机运行解释模式,-Xcomp强制虚拟机运行编译模式,只是在无法编译的情况下使用解释器运行。【mixed mode | compiled mode | interpreted mode】
即时编译本地代码需要占用程序运行时间,解释器还需要收集性能监控信息,对解释器的执行速度有影响。
HotSpot虚拟机内置两个即时编译器:Client Compiler和Server Compiler,称为C1和C2,分别用在客户端和服务端。C1能够获取更高的编译速度,而C2获取更好的编译质量。
3. 编译对象与触发条件
在运行期会被JVM编译即时编译器编译的对象包括【热点代码】:
- 被多次调用的方法
这情况,是由方法调用触发的编译,编译器会将整个方法作为编译对象; - 被多次执行的循环体
这中情况解决一个方法调用少次,但方法体内存在循环次数较多循环体问题,这样循环体代码也被重复执行多次,也认为是热点代码。编译器依然会将整个方法作为编译对象。
编译热点代码触发的阈值在client下默认1500次,server模式下默认10000次,可以通过-XX:compileThreshold设定。如果不做设置方法统计执行次数是在一定时间内被调用的次数,如果超过一定是不被调用,则计数器将会减半,这个过程称为“计数器热度衰退”,可以通过 -XX:UseCounterDecay来关闭热度衰退,还可以通过 -XX:CounterHalfLifeTime设置半衰退时间周期(单位秒)。
4. 编译器优化
当JVM编译代码时,它将汇编指令保存在代码缓存,代码缓存具有固定大小,一旦它被填满,JVM将不能编译更多的代码。 -XX:ReservedCodeCacheSize 增加代码缓存的大小(默认32m)。编译器线程的数量可以通过 -XX:CICompilerCount=N flag 进行调节设置。
即时编译在编译期的优化策略:
- 方法内联
- 冗余访问消除
- 无用代码消除
- 公共子表达式消除
- 数组范围检查消除
- 逃逸分析
- 其他N多策略
5. 即时编译器日志
public class TrueFalseVO {
private static final int NUM=15000;
private static int doubleValue(int i) {
for (int j = 0; j < NUM; j++) {
}
return i*2;
}
private static long caclSum(){
long sum = 0;
for (int i = 0; i < 100; i++) {
sum = doubleValue(i) + sum;
}
return sum;
}
/**
* -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
* PrintCompilation打印及时编译信息
* PrintInlining打印方法内联信息
*/
public static void main(String[] args){
for (int i = 0; i < NUM; i++) {
caclSum();
}
}
}
// 即时编译日志
189 1 n 0 java.lang.System::arraycopy (native) (static)
189 2 n 0 java.lang.Thread::currentThread (native) (static)
195 3 3 java.lang.AbstractStringBuilder::<init> (12 bytes)
195 4 4 java.lang.String::hashCode (55 bytes)
196 5 4 java.lang.String::charAt (29 bytes)
197 6 1 sun.instrument.TransformerManager::getSnapshotTransformerList (5 bytes)
199 7 3 java.lang.Character::toLowerCase (6 bytes)
199 8 3 sun.nio.cs.UTF_8$Encoder::encode (359 bytes)
200 9 3 java.lang.String::length (6 bytes)
201 12 3 java.lang.Object::<init> (1 bytes)
201 10 3 java.lang.Math::min (11 bytes)
201 13 3 java.lang.String::indexOf (7 bytes)
201 14 4 java.lang.String::indexOf (70 bytes)
201 11 3 java.lang.ref.SoftReference::get (29 bytes)
204 15 3 java.lang.ThreadLocal::getMap (5 bytes)
204 16 3 java.lang.String::startsWith (72 bytes)
204 17 3 java.lang.String::startsWith (7 bytes)
204 18 3 java.util.zip.ZipFile::ensureOpen (37 bytes)
204 19 3 java.util.zip.ZipCoder::getBytes (192 bytes)
207 25 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes)
207 24 3 java.lang.String::<init> (82 bytes)
207 23 3 java.lang.StringBuilder::append (8 bytes)
207 20 3 java.util.zip.ZipCoder::encoder (35 bytes)
208 21 3 java.nio.charset.CharsetEncoder::reset (11 bytes)
208 22 1 java.lang.ThreadLocal::access$400 (5 bytes)
208 26 s 1 java.util.Vector::size (5 bytes)
210 27 % 3 com.netease.ecnrypt.Main::doubleValue @ 2 (19 bytes)
210 28 3 com.netease.ecnrypt.Main::doubleValue (19 bytes)
211 29 % 4 com.netease.ecnrypt.Main::doubleValue @ 2 (19 bytes)
211 27 % 3 com.netease.ecnrypt.Main::doubleValue @ -2 (19 bytes) made not entrant
212 30 4 com.netease.ecnrypt.Main::doubleValue (19 bytes)
212 28 3 com.netease.ecnrypt.Main::doubleValue (19 bytes) made not entrant
214 31 3 com.netease.ecnrypt.Main::caclSum (26 bytes)
215 32 % 4 com.netease.ecnrypt.Main::caclSum @ 4 (26 bytes)
217 33 4 com.netease.ecnrypt.Main::caclSum (26 bytes)
218 31 3 com.netease.ecnrypt.Main::caclSum (26 bytes) made not entrant
JIT即时编译: https://www.ibm.com/developerworks/cn/java/j-lo-just-in-time/index.html
《Java虚拟机第二版》