从0到1理解JVM_ 即时编译器JIT

JIT 概念

JIT,即时编译器(just in time compiler),是 JVM 的一种优化技术。最开始的时候,JAVA 代码被编译为字节码,字节码在各个平台上都被解释执行,这给 JAVA 带来了平台无关性的优点,也带来了执行速度慢的缺点。

为了改进该缺点,HotSpot 虚拟机在执行字节码时,若发现某个方法或代码块执行过于频繁,会把它们定义为“热点代码“,尝试性将字节码进一步编译为本地代码,不再解释字节码,而是执行机器语言,JIT 技术,指的就是这个改进。

谁会被编译

既然 JIT 会将代码编译为机器码,那它编译谁呢?

被编译的代码有两种,一种是频繁调用的方法,另一种是频繁调用的循环体(如 for 循环、while 循环)。

所谓“频繁调用的方法”,顾名思义,就是调用次数多的方法,会被编译成本地代码。频繁调用的循环体,其所在的方法会被当做“频繁调用的方法”进行编译(这就是“栈上替换“,当循环体所在的方法仍旧在栈上执行的同时,就被编译并替换)。

总之,被JIT技术编译的只有方法和循环体。

怎样才会启动编译(热点探测)

什么时候才编译方法呢?什么时候才能判定一个方法或循环体是热点代码呢?HotSpot 虚拟机使用了基于计数器的热点探测。

HotSpot 虚拟机给每个方法都加了两个计数器:方法调用计数器、回边计数器。每当方法调用一次时,计数器就加1,计数器达到一定的阈值后,就启动编译进程,将方法编译为本地代码,下一次调用该方法时,就执行编译后的本地代码。

如何编译

JIT 有两种编译器:client 版和 server版。

client编译器是个三阶段的编译器,第一阶段做方法内联/常量传播等优化,将字节码编成高级中间表示(HIR),第二阶段做 null 检查/范围检查等优化,将 HIR 编成低级中间表示(LIR), 第三阶段则是分配寄存器和生成机器码。

server 编译器会执行所有的优化动作,如方法内联,公共子表达式消除,复制传播,常量传播,无用代码消除,逃逸分析等等,优化后的代码执行速度堪比编译型语言如 C、C++等!

逃逸分析

所谓逃逸,就是一个对象在某方法里定义后,会被外部引用。

如果能确定一个对象不会逃逸,那么就可以将对象分配到栈上(栈上分配),尽可能不使用堆,减小 GC 的压力;同时,既然对象不会被外部引用,在同步时就不必考虑该对象了,减小了线程同步的复杂性;另外,还能进行标量替换(用大量的分配在栈上的原始数据类型代替对象)。

如今逃逸分析尚未成熟,是 JIT 发展的重要方向。