本篇文章主要介绍了" Java性能优化指南系列三):理解JIT编译器",主要涉及到方面的内容,对于系统运维感兴趣的同学可以参考一下:
即时编译器概述编译器在编译过程中通常会考虑很多因素。比如:汇编指令的顺序。假设我们要将两个寄存器的进行相加,执行这个操作一般只需要一个CPU周期;但是在相加之前...
即时编译器概述
- 编译器在编译过程中通常会考虑很多因素。比如:汇编指令的顺序。假设我们要将两个寄存器的值进行相加,执行这个操作一般只需要一个CPU周期;但是在相加之前需要将数据从内存读到寄存器中,这个操作是需要多个CPU周期的。编译器一般可以做到,先启动数据加载操作,然后执行其它指令,等数据加载完成后,再执行相加操作。由于解释器在解释执行的过程中,每次只能看到一行代码,所以很难生成上述这样的高效指令序列。而编译器可以事先看到所有代码,因此,一般来说,解释性代码比编译性代码要慢。不过,解释性代码具有可移植性的优势。
- Java的实现在解释性和编译性之间进行了折中。Java代码是编译性的,它会被编译成一个平台独立的字节码程序。JVM负责加载、解释、执行这些字节码程序,在这个过程中,还可能会将这些字节码实时编译成目标机器码,以便提升性能。
- 在本章中,我们主要关注JVM是如何解释、执行、编译字节码的。
编译热点代码
- JVM在解释执行字节码的时候,不会立即对它进行编译。主要原因有两个:
- 如果代码只执行一次,对代码进行编译得不偿失(编译之后还要执行代码)。
- 代码执行的次数越多,JVM可以获取到的信息就越多。JVM就可以在编译代码的时候采用更多的优化手段。比如:JVM经常执行equals()方法,b = obj1.equals(obj2)。JVM需要根据obj1找到它的类型,然后才知道应该执行那个equals函数。这个过程是比较费时的,为了加快执行速度,编译代码的时候可以将类型查找的过程优化掉,直接执行String.equals(obj2)。不过,在实际代码中,可能不会这么简单,obj1的类型会发生变化。但是只要代码执行次数够多,优化后,性能就会有比较大的提升。
基本调优:Client或Server
- 即时编译器有两种类型,client和server。一般情况下,对编译器进行优化,唯一要做的就是选择那一类编译器。
- 可以通过在启动java的命令中,传入参数(-client或-server)来选择编译器(C1或C2)。这两种编译器的最大区别就是,编译代码的时间点不一样。client编译器(C1)会更早地对代码进行编译,因此,在程序刚启动的时候,client编译器比server编译器执行得更快。而server编译器会收集更多的信息,然后才对代码进行编译优化,因此,server编译器最终可以产生比client编译器更优秀的代码。
- 可能大家都有一个困扰,JVM为什么要将编译器分为client和server,为什么不在程序启动时,使用client编译器,在程序运行一段时间后,自动切换为server编译器?其实,这种技术是存在的,一般称之为: tiered compilation。Java7
和Java 8可以使用选项-XX:+TieredCompilation来打开(-server选项也要打开)。在Java 8中,-XX:+TieredCompilation默认是打开的。
启动优化

可以看到,对于中等大小的GUI应用,使用client编译器,启动时间最短,有将近38.5%的提升。对于大型应用,使用各种编译器的启动时间差别不大。
批处理应用优化
- 下图是对第2章中股票应用的性能测试结果,第1列是股票的个数:

观察上图,可以得出以下结论: