## 8.1 解释和编译 Java VM 使用了JIT(Just-in-time简称,程序即时编译) 编译器,在运行时刻进将字节码通过模板方式转为高效的机器码,也可以在运行时候通过分析,进一步编译成更加高效的机器码,因此现在Java程序,已经能跟C和C++具有一样的性能 对于操作系统来说,识别的是机器码指令,因此像C,C++高级语言都是将源程序编译成二进制码目标文件,然后链接成库文件或者可执行文件,这些二进制针对特定的平台,比如WINDOW,Linux。 这种方式成为AOT(Ahead-of-time,在执行执行前编译),对于PHP,则采用解释执行,可以运行在任何平台上,只要在平台上安装正确的解释器。解释器会翻译PHP代码成二进制代码执行,如果PHP被反复执行,则需要一遍又一遍翻译成机器码。解释执行的效率比编译后执行的效率低很多 > 注:PHP8也开始支持JIT 解释执行的优点是可以直接在任何平台上运行,不需要再编译,另外,如果代码更改,也可以直接解释执行而不需要再次编译成目标平台的机器码。 JVM的解释执行使用基于模板的解释器,比如,对于字节码 ``` iload_1 invokestatic ... ``` 第一个字节码`iload_1`映射成机器码 ``` MOV -0x8(%r14), %eax # ILOAD_1 movzbl 0x1(%r13), %ebx # 下一个指令 inc %r13 mov $0xff40,%r10 jmpq *(%r10,%rbx,8) ``` 虚拟机对于最常用的代码,会尝试编译成机器码放到"Code Cache 代码缓存“里,如下图所示 ![img001](https://img.kancloud.cn/4a/f4/4af4fd11b10ad6c9485a6e1098905b1b_1292x464.png) JIT也会做其他优化,比如,对于多态调用,采用的是虚方法表中查找,在JIT在执行多次后,如果收集到足够的信息,会取消多态掉调用的代码,改为直接调用。如果随后发现存在多态调用,则可能取消这部分优化,称之为逆优化(Deoptimization). 关于虚方法调用,可以参考8.6 对于小的方法,JIT也可以优化为内联调用,比如属性字段的getter和 setter方法 ~~~java public int getAge(){ return age; } ~~~ 当调用user.getAge()的时候,JIT会直接优化成user.age对应的机器码。关于内联优化,可以参考8.5。 通过虚拟机选项 -Xint 可以强制使用解释执行,如下JMH测试用于对比解释执行和编译机器码执行的性能 ```java @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 10) @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) @Threads(1) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) public class ClientTest { int x=0,y=0; @Benchmark @Fork(value=1,jvmArgsAppend="-Xint") @CompilerControl(CompilerControl.Mode.DONT_INLINE) public int bytecode(){ return x+y; } @Benchmark @Fork(value=1) @CompilerControl(CompilerControl.Mode.DONT_INLINE) public int machinecode(){ return x+y; } } ``` bytecode使用了`@Fork(value=1,jvmArgsAppend="-Xint")` 方法,解释执行。其性能远远低于machinecode方法 ``` Benchmark Mode Score Units c.i.c.j.ClientTest.bytecode avgt 202.944 ns/op c.i.c.j.ClientTest.machinecode avgt 2.724 ns/op ```