目录
一、解释器与编译器
二、即时编译器
1. 热点代码
2. 编译过程
三、编译器优化技术
1. 方法内联
2. 逃逸分析
3. 公共子表达式
4. 数组边界检查消除
四、参考资料
一、解释器与编译器
HotSpot虚拟机采用解释器与编译器并存的运行架构,如下表所示两者特点。
解释与编译器 | 特点 |
解释器 | 1.目的:字节码文件转为本地机器码,如:javac编译后,解释器解释; 2.优点:程序可以迅速启动和执行,省去编译的时间,立即运行。 |
编译器 | 1.目的:即时编译出优化程度更高的本地机器码,但花费时间也会越长; 2.HotSpot虚拟机内置两个或三个即时编译器,其中两个即时编译: C1编译器:客户端编译器(Client Compiler); C2编译器:服务端编译器(Server Compiler),JDK10的Graal替换C2; |
注意: a.JVM中一般采用“混合模式”(mixed mode):解释器与编译器搭配使用; b.参数-client、-server强制JVM运行在客户端模式,还是服务端模式。 |
输出代码优化质量的高低决定编译器是否优秀的关键,编译器分为:前端编译器(将Java源码转变成字节码的过程,只完成源码到抽象语法树或字节码的生成,如:javac命令)、后端编译器(完成代码优化及字节码生成本地机器码的过程,如:即时编译、提前编译都是后端编译器)。后端编译器完成热点代码的编译,又分为即时编译、提前编译;而HotSpot即时编译内置不同模式下的C1、C2编译。
即时编译最大缺点是运行时编译占用程序运行时间或运算资源,为了提高效率,则提前编译器编译后提前保存(即时编译缓存),运行时则直接加载使用。
JDK7在服务端模式,默认开启编译策略,即:分层编译,根据编译、优化的规模与耗时划分出不同的层次,如下图所示编译分层及交互关系。
二、即时编译器
1. 热点代码
程序运行过程中对“热点代码”进行即时编译,所以即时编译的对象是热点代码。热点代码有:多次调用方法、多次执行的循环体,如下表所示。
热点代码分类 | 特点 |
被多次调用的方法 | 整个方法体作为编译对象(标准的即时编译方式) |
被多次执行的循环体 | 1.热点代码只是方法体的一部分,但是以整个方法作为编译对象; 2.“栈上替换”:虽然以整个方法作为编译对象,但是执行入口(从方法第几条字节码指令)不同,编译时会传入执行入口点字节码序号。这种编译发生在方法执行过程中,即:方法的栈帧还在栈中,方法被替换。 |
注意: a.运行时,即时编译的目标是“热点代码”,有更好的优化; b.“栈上替换”:方法的栈帧还在栈中,方法被替换; c.上述两种热点代码,都是以整个方法作为编译对象,但是执行入口不同。 |
注意:以上两种热点代码,编译的对象都是整个方法体,而不是单独的循环体。若是循环体,执行入口(从方法第几条字节码指令开始执行)会稍有不同。
那么什么时候触发即时编译呢?即热点代码探测技术,其分为:采样、计数器(JVM采用),如下表所示。
热点代码探测 | 特点 | |
采样 | 1.实现:周期性检查各线程的调用栈顶,发现某个方法经常出现,则为热点; 2.优点:简单高效;易获取方法调用关系; 缺点:很难精确方法热度,易受线程阻塞干扰。 | |
计数器 (HotSpot使用) | 1.实现:每个方法(或代码块)建计数器,某时间段内统计执行次数,超出该阈值,则为热点; 2.优点:更加精确严谨; 缺点:实现复杂(维护计数器);不能直接获取方法调用关系; 3.HotSpot每个方法有两类计数器:方法调用计数器、回边计数器; a.方法调用计数器:参数-XX:CompileThreshold(客户端模式1500次;服务端模式10000次);热度衰减(衰减操作在GC时触发); b.回边计数器:不同模式下回边阈值统计不同;绝对次数,没有热度衰减。 | |
注意: a.“回边”(Back Edge):字节码控制流遇到向后跳转的指令; b.回边次数不等于循环次数,如:空循环(自己跳转到自己的过程); c.HotSpot两类计数器触发即时编译,见下图。 |
2. 编译过程
即时编译代码优化是建立在中间表示或机器码之上,而不是直接在源码或字节码做优化,如下图所示编译过程,共三个阶段:字节码生成HIR(High-Level Intermediate Representation _ 高级中间代码)、HIR生成LIR(Low-Level Intermediate Representation _ 低级中间代码)、LIR生成本地机器码。
三、编译器优化技术
1. 方法内联
方法内联被称为“优化之母”,为其他优化建立良好的基础。方法内联是把目标方法的代码“复制”到主调方法之中,避免发生真实的方法调用。
2. 逃逸分析
逃逸分析有方法逃逸、线程逃逸两种:
- 方法逃逸:方法内定义一个对象,它可能被其他方法所引用,如:调用参数传递到其他方法;
- 线程逃逸:线程内定义一个对象,它可能被另一线程所访问,如:赋值可被其他线程访问。
3. 公共子表达式
表达式E已经被计算过,且从先前到现在E中所有变量没有改变,则E这次出现称为“公共子表达式”。
4. 数组边界检查消除
每次访问数组元素时都要边界检查(性能负担),提高效率则编译期间完成边界检查。
四、参考资料
HotSpot虚拟机之Class文件及字节码指令_爱我所爱0505的博客-CSDN博客
HotSpot虚拟机之类加载过程及类加载器_爱我所爱0505的博客-CSDN博客
HotSpot虚拟机之字节码执行引擎_爱我所爱0505的博客-CSDN博客
什么是即时编译_MariaOzawa的博客-CSDN博客
JIT即时编译器_rockvine的博客-CSDN博客
逃逸分析之标量替换_chengqiuming的博客-CSDN博客
JVM系列之:深入学习方法内联 - 知乎