1.前端编译与后端编译
- 前端编译:Java文件编译为class文件
- 后端编译:class字节码编译为机器指令
2.字节码指令如何执行?
- 解释执行:按字翻译为机器码。
- 编译执行:将要执行的字节码指令提前编译维护到CodeCache中,执行时从缓存中取。
- 即时编译器JIT:将热点代码提前编译,放进缓存。
java -version 可以看到目前用的什么执行方式。
- mixed mode 混合模式
- interpreted mode 解释执行模式
- compiler mode 编译执行模式 由于内存限制,启动预热时间要求默认使用混合模式
3.热点代码识别
- 热点探测:判断代码是不是热点,需不需要即时编译。hotspot采用基于计数器的热点探测方法,计数器数值达到阈值触发即时编译。
- 方法调用计数器
- 回边计数器:统计循环体代码执行次数
- 方法调用计数器
4.客户端编译器C1与服务端编译器C2
- C1:相当于是一个初级翻译。编译过程中,C1会对字节码进行简单和可靠的优化,耗时短,以达到更快的编译速度。启动快,占用内存小。但是翻译出来的机器码优化程度不太高。比较适合于一些小巧的桌面应用,因此也称为客户端编译器。
- C2:相当于是一个高级翻译。编译过程中,C2会对字节码进行更激进的优化,优化后的佮代码执行效率更高。但是相应的,工作量也变得更大了。C2的启动更慢,占用内存也更多。进行耗时较长的优化,以及激进优化,但优化的代码执行效率更高。启动慢,占用内存多,执行效率高。比较适合于一些资源充裕的服务级应用,因此也称为服务端编译器。
4.1 分层编译
由于即时编译器编译本地代码需要占用程序运行时间,通常要编译出优化程度越高的代码,所花费的时间便会越长;而且想要编译出优化程度更高的代码,解释器可能还要替编译器收集性能监控信息,这对解释执行阶段的速度也有所影响。为了在程序启动响应速度与运行效率之间达到最佳平衡,HotSpot虚拟机在编译子系统中加入了分层编译的功能,分层编译根据编译器编译、优化的规模与耗时,划分出不同的编译层次。
| 等级 | 描述 | 性能 |
|---|---|---|
| 0 | 程序纯解释执行,并且解释器不开启性能监控功能(Profiling) | 1 |
| 1 | 使用C1编译器将字节码编译为本地代码来运行,进行简单可靠的稳定优化。不开启性能监控功能。 | 4 |
| 2 | 仍然使用C1编译器执行,仅开启方法及回边次数统计等有限的性能监控功能。 | 3 |
| 3 | 仍然使用C1编译器执行,开启全部性能监控,除了第2层的统计信息外,还会收集如分支跳转、虚方法调用版本等全部的统计信息。 | 2 |
| 4 | 使用C2编译器将字节码编译为本地代码,相比起C1编译器,C2编译器会启用更多编译耗时更长的优化,还会根据性能监控信息进行一些不可靠的激进优化。 | 5 |
public class JitDemo {
private int add(int x){
return x+1;
}
public static void main(String[] args) {
int a = 0;
JitDemo demo = new JitDemo();
long l = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
a = demo.add(a);
}
System.ou
.println("a= "+a);
System.out.println(">>>>>>>>"+(System.currentTimeMillis()-l));
}
}
-
JDK8 中提供了参数 -XX:TieredStopAtLevel=1 可以指定使用哪一层编译模型。
-
使用-Xint , -Xcomp, -XX:TieredStopAtLevel=1,-XX:TieredStopAtLevel=5比较
5.后端编译优化技术
wiki.openjdk.java.net/display/Hot…
5.1 方法内联
- 方法内联的前提是要足够的循环次数,成为热点代码
- -XX:+Inline 启用方法内联。默认开启。
- -XX:InlineSmallCode=size 用来判断是否需要对方法进行内联优化。如果一个方法编译后的字节码大小大于这个值,就无法进行内联。默认值是1000bytes。
- -XX:MaxInlineSize=size 设定内联方法的最大字节数。如果一个方法编译后的字节码大于这个值,则无法进行内联。默认值是35byt
- -XX:FreqInlineSize=size 设定热点方法进行内联的最大字节数。如果一个热点方法编译后的字节码大于这个值,则无法进行内联。默认值是325bytes。
- -XX:MaxTrivialSize=size 设定要进行内联的琐碎方法的最大字节数(Trivial Method:通常指那些只包含一两行语句,并且逻辑非常简单的方法。比如像这样的方法
提高方法内联的概率:
- 在编程中,尽量多写小方法,避免写大方法。方法太大不光会导致方法无法内联,另外,成为热点方法后,还会占用更多的CodeCache。
- 在内存不紧张的情况下,可以通过调整JVM参数,减少热点阈值或增加方法体阈值,让更多的方法可以进行内联。
- 尽量使用final, private,static关键字修饰方法。方法如果需要继承(也就是需要使用invokevirtual指令调用),那么具体调用的方法,就只能在运行这一行代码时才能确定,编译器很难在编译时得出绝对正确的结论,也就加大了编译执行的难度。
5.2 逃逸分析
- 对象在方法体中被定义之后,有没有被外部方法引用。
- 例如:作为参数传递到外部方法中或者被其他线程访问。
- 从不逃逸、方法逃逸到线程逃逸,称为对象由低到高的不同逃逸程度。
- 如果能证明对象不会逃逸,那么这种的就可以进一步优化。
5.2.1 标量替换
把对象拆解为基本的数据类型,用来替代原来的对象。
5.2.2 栈上分配
标量替换完成后,分配在栈帧之类,方法出栈后自动销毁,不用经过gc。
6.锁消除
消除jvm确认无用的不生效的synchronized关键字