即时(JIT)编译器是Java运行时环境的一个组件,可提高运行时Java应用程序的性能。JVM中没有什么比编译器对性能的影响更大,并且选择编译器是运行Java应用程序时做出的首要决定之一,无论您是Java开发人员还是最终用户。

Java JIT编译器:概述

Java功能“写一次,到处运行”的关键是 bytecode。字节码转换为适用于应用程序的适当本机指令的方式对应用程序的速度有很大的影响。这些字节码可以被解释,编译为本机代码或直接在指令集体系结构为字节码规范的处理器上执行。解释字节码是Java虚拟机(JVM)的标准实现,这会使程序的执行速度变慢。为了提高性能,JIT编译器在运行时与JVM交互,并将适当的字节码序列编译为本地机器代码。使用JIT编译器时,硬件可以执行本机代码,而不是让JVM重复解释相同的字节码序列,并导致翻译过程相对冗长。这样可以提高执行速度,除非方法执行频率较低。JIT编译器编译字节码所花费的时间被添加到总体执行时间中,并且如果不频繁调用JIT编译的方法,则可能导致执行时间比用于执行字节码的解释器更长。当将字节码编译为本地代码时,JIT编译器会执行某些优化。由于JIT编译器将一系列字节码转换为本机指令,因此它可以执行一些简单的优化。JIT编译器执行的一些常见优化操作包括数据分析,从堆栈操作到寄存器操作的转换,通过寄存器分配减少内存访问,消除常见子表达式等。JIT编译器进行的优化程度越高,在执行阶段花费的时间越多。因此,JIT编译器不能承担静态编译器所做的所有优化,这既是因为增加了执行时间的开销,又是因为它只对程序进行了限制。

它是如何工作的 ?!

即时(JIT)编译器是Java运行时环境的一个组件,可提高运行时Java应用程序的性能。Java程序由类组成,这些类包含平台中立的字节码,可以由JVM在许多不同的计算机体系结构上解释。在运行时,JVM加载类文件,确定每个单个字节码的语义,并执行适当的计算。解释过程中额外的处理器和内存使用情况意味着Java应用程序比本地应用程序执行得更慢。JIT编译器通过在运行时将字节码编译为本机机器代码来帮助提高Java程序的性能。

JIT编译器默认情况下处于启用状态,并在调用Java方法时被激活。JIT编译器将该方法的字节码编译为本地机器代码,“及时”编译以运行。编译方法后,JVM会直接调用该方法的已编译代码,而不是对其进行解释。从理论上讲,如果编译不需要处理器时间和内存使用量,则编译每种方法都可以使Java程序的速度接近本机应用程序的速度。
JIT编译确实需要处理器时间和内存使用率。JVM首次启动时,将调用数千种方法。即使程序最终达到了非常好的峰值性能,编译所有这些方法也会严重影响启动时间。

不同应用程序的不同编译器

JIT编译器有两种形式,并且选择使用哪个编译器通常是运行应用程序时唯一需要进行的编译器调整。实际上,即使在安装Java之前,也要考虑知道要选择哪个编译器,因为不同的Java二进制文件包含不同的编译器。

客户端编译器

著名的优化编译器是C1,它是通过-clientJVM启动选项启用的编译器。就像启动名称所暗示的那样,C1是客户端编译器。它是为客户端应用程序设计的,这些客户端应用程序具有较少的可用资源,并且在许多情况下对应用程序启动时间敏感。C1使用性能计数器进行代码性能分析,以实现简单的,相对无干扰的优化。

服务器端编译器

对于长时间运行的应用程序(例如服务器端企业Java应用程序),客户端编译器可能不够。可以使用类似C2的服务器端编译器。通常通过将JVM启动选项添加-server到启动命令行来启用C2 。由于大多数服务器端程序预计将运行很长时间,因此启用C2意味着您将能够比运行短时间的轻量级客户端应用程序收集更多的性能分析数据。因此,您将能够应用更高级的优化技术和算法。

分层编译

分层编译结合了客户端编译和服务器端编译。分层编译利用了JVM中客户端和服务器编译器的优势。客户端编译器在应用程序启动期间最活跃,并处理由较低的性能计数器阈值触发的优化。客户端编译器还会插入性能计数器,并准备指令集以进行更高级的优化,服务器端编译器将在稍后阶段解决这些问题。分层编译是一种非常节省资源的性能分析方法,因为编译器能够在影响较小的编译器活动期间收集数据,以后可以将其用于更高级的优化。与仅使用解释的代码配置文件计数器所获得的信息相比,这种方法还可以产生更多的信息。

代码优化

选择编译方法后,JVM会将其字节码提供给即时编译器(JIT)。JIT必须先了解字节码的语义和语法,然后才能正确编译该方法。为了帮助JIT编译器分析该方法,首先将其字节码重新格式化为称为trees,它比字节码更类似于机器代码。然后对方法的树进行分析和优化。最后,将树翻译成本地代码。JIT编译器可以使用多个编译线程来执行JIT编译任务。使用多个线程可以潜在地帮助Java应用程序更快地启动。实际上,只有在系统中有未使用的处理核心的情况下,多个JIT编译线程才能显示性能的提高。编译线程的默认数量由JVM标识,并且取决于系统配置。如果生成
的线程数不是最佳的,则可以使用该XcompilationThreads选项覆盖JVM决策。有关使用此选项的信息,请参见JIT和AOT命令行选项。

编译包括以下几个阶段:

内联

内联是将较小方法的树合并或“内联”到其调用者的树中的过程。这加快了频繁执行的方法调用。

局部优化

局部优化可以一次分析和改进一小部分代码。许多本地优化实现了经典静态编译器中使用的久经考验的技术。

控制流优化

控制流优化分析方法(或方法的特定部分)内部的控制流,并重新排列代码路径以提高其效率。

全局优化

全局优化可一次对整个方法起作用。它们更加“昂贵”,需要大量的编译时间,但可以大大提高性能。

本机代码生成

本地代码生成过程因平台架构而异。通常,在编译的此阶段,方法的树将转换为机器代码指令;根据架构特征执行一些小的优化。


进一步阅读: