即时编译器(JIT)是什么?

635

相对于非JIT编译器,JIT编译器具体做什么? 能否简洁易懂地描述一下?

JIT编译器会在程序运行时动态编译字节码为本机代码,从而提高程序的执行效率。而非JIT编译器则会在程序启动时将整个程序的字节码编译为本机代码,这可能会导致启动时间延长。

1
http://www.ibm.com/support/knowledgecenter/SSYKE2_7.1.0/com.ibm.java.aix.71.doc/diag/understanding/jit_overview.html - TechDog
2
更新链接 - Akash Narayan
2
我发现https://www.youtube.com/watch?v=yQ27DjKnxwo很有用。 - Adam Zerner
18个回答

615
一种JIT编译器在程序启动后运行,将代码(通常是字节码或某种虚拟机指令)即时编译成通常更快的本地指令集形式(通常是主机CPU的本地指令集)。与标准编译器不同,JIT具有访问动态运行时信息的能力,并且可以进行更好的优化,例如内联频繁使用的函数。
这与传统编译器完全不同,传统编译器会在首次运行程序之前将所有代码编译为机器语言。
换句话说,传统编译器会在第一次运行程序之前将整个程序构建为一个EXE文件。对于较新的程序,会生成具有伪代码(p-code)的汇编代码。仅当在操作系统上执行程序(例如,通过双击其图标)时,JIT编译器才会启动并生成Intel处理器或其他处理器可理解的机器码(m-code)。

31
与解释型代码相比,编译型代码会立即开始运行字节码或虚拟机指令,但是执行速度比机器语言慢。 - Aaron
5
JIT通常与解释性代码一起使用,将其转换为机器语言,但没有任何JIT的纯解释性代码是很慢的。甚至没有JIT的Java字节码也非常慢。 - Mark Cidade
56
目标不必是机器码。JRuby具有JIT编译器,可以在几次调用后将Ruby源代码编译为Java字节码,然后在另外几次调用后,JVM的JIT编译器会将字节码编译为本地代码。 - Jörg W Mittag
12
值得注意的是,正如Jörg所暗示的那样,即时编译并不一定会立即调用。通常情况下,代码将被解释执行,直到确定它值得进行即时编译。由于即时编译可能会引入延迟,因此如果某些代码很少使用且快速响应比总运行时间更重要,则不进行即时编译可能会更快。 - Adam Jaskiewicz
4
如果推出了一种新机器,使用传统编译器对程序进行编译和优化,可能比使用即时编译器更快地产生结果。另一方面,为该新机器进行优化的即时编译器将能够优化在该新机器发明之前发布的代码的性能。 - supercat
显示剩余13条评论

288

起初,编译器负责将高级语言(定义为比汇编语言更高级的语言)转换为目标代码(机器指令),随后由链接器将其链接成可执行文件。

在语言演变的某个时期,编译器会将高级语言编译成伪代码,然后由解释器解释运行程序。这样消除了目标代码和可执行文件,使得这些语言可以在多个操作系统和硬件平台上移植。 Pascal(编译为P-Code)是最早的一种; Java和C#是较近期的例子。最终,“P-Code”一词被替换为“字节码”,因为大多数伪操作只有一个字节长。

即时编译(JIT)编译器是运行时解释器的一项功能,它不是每次调用方法就解释字节码,而是将字节码编译成运行机器的机器代码指令,然后调用该目标代码。理想情况下,运行目标代码的效率将克服每次运行程序重新编译的低效率。


5
然而,这个短语“Just-In-Time (JIT)编译器是运行时解释器的一个特性”造成了困惑,例如:https://dev59.com/p2Qn5IYBdhLWcg3w9K_3#16440092。 - Stephen C
12
实际上,JIT是一个插件,您仍然可以使用Java的-Xint参数禁用它,因此它只是一项功能。 - Craig Trader
3
我不完全同意。JIT 不是进化,而是经典编译器的替代品。 - i486
2
JIT是从硬连机械开关到通过对智能手机说“OK Google”指定搜索条件的进化路径上的一步。作为Java 7/8的一部分可用的当前JIT远远超出了Java 2可用的范围 - 这也是进化。 - Craig Trader
1
@i486 - 据我所知,Sun / Oracle从未提供过生成本地代码的经典(“预先编译”)Java编译器。认为JIT是一种替代方案有些牵强...因为他们认为它是替代方案的东西从未被提供。(我不考虑GCJ AOT编译器,因为那与Sun / Oracle无关,而且它也不是一个完整的解决方案。现在肯定是不可行的。) - Stephen C
显示剩余3条评论

81

JIT-即时编译

这个词本身就表明了只在需要时(按需)进行编译。

典型场景:

源代码完全转换为机器代码。

JIT场景:

源代码将被转换成类似汇编语言的结构[例如C#的IL(中间语言),Java的字节码]。

当应用程序需要时,中间代码才会被转换成机器语言,也就是仅将所需的代码转换为机器代码。

JIT与非JIT比较:

  • 在JIT中,并非所有代码都被转换为机器代码,而只有必要的部分代码会被转换成机器代码;如果某个方法或功能不在机器代码中,则会被转换为机器代码…… 这减轻了CPU的负担。

  • 由于机器代码将在运行时生成…… JIT编译器将生成针对运行机器CPU架构进行优化的机器代码。

JIT示例:

  1. 在Java中,JIT位于JVM(Java虚拟机)中
  2. 在C#中,它位于CLR(公共语言运行时)中
  3. 在Android中,它位于DVM(Dalvik虚拟机)中,或在较新版本中则是ART(Android RunTime)。

8
JIT在支持真正泛型类型的框架中具有一些特殊优势;可以定义一个泛型方法,能够生成无限范围的类型,每种类型都需要不同的机器码,但是JIT只为实际产生的类型生成代码。相比之下,在C++中,编译器需要为程序将要使用的所有类型生成代码。 - supercat
8
JVM第一次运行代码时不会进行即时编译(JIT)。前几次,它会解释字节码。如果该代码运行足够频繁,JVM可能会决定对其进行JIT编译。 - ninjalj
1
你是说Java中的JIT就是JVM。但是我们已经向JVM提供了编译好的代码,不是吗?那么你的意思是它会再次编译它吗? - Koray Tugay
@KorayTugay - 我们向JVM提供字节码,JVM会按需将其中的一部分转换为机器码,从而节省资源。 - Durai Amuthan.H
1
在Java中,JIT不是JVM,它只是其中的一部分。 - happs
显示剩余5条评论

37

正如其他人所提到的

JIT代表即时编译,这意味着代码在需要时进行编译,而不是在运行时之前。

只是为了补充一下以上讨论的一个观点,在JVM中维护了一个计数器来记录一个函数被执行的次数。如果该计数器超过预定的限制,则JIT将代码编译成机器语言,可直接由处理器执行(与一般情况下javac将代码编译成字节码,然后java解释器逐行解释此字节码并将其转换为机器代码再执行不同)。

此外,在下次计算此函数时,将再次执行相同的编译代码,不像普通解释方式下,代码会再次逐行解释。这使得执行更快。


29

JIT编译器只在首次执行时将字节码编译为等效的本地代码。在每次后续执行中,JVM仅使用已经编译好的本地代码来优化性能。

enter image description here

没有JIT编译器,JVM解释器会逐行翻译字节码,使其看起来像执行本机应用程序。

enter image description here

来源


3
我对JIT的理解是它类似于记忆化,经常使用的函数会被“存储”,从Java字节码到本机ISA相关代码的编译费用也将被绕过。如果我的理解正确,为什么Java不从一开始就完全编译成本地代码呢?这将减少任何类型的运行时编译并使Java“本地化”到机器上。 - Michael Choi
2
因为这将延迟应用程序的启动。JIT使快速启动成为可能,并加速应用程序执行。这是一个权衡。 - Kacper Cichecki
我不知道字节码是解释性的,谢谢你提供的信息。 - ShifraSec
我也查看了源代码,但我认为这不是典型的JVM实现。这取决于具体的实现,但我认为这不是通常的现代JVM。请问有人能否澄清一下这个答案是否正确,或者现代JVM是否不同? - starriet

17

JIT代表即时编译,这意味着代码在需要时才被编译而不是在运行时之前。

这是有益的,因为编译器可以为您特定的计算机生成优化的代码。 静态编译器(如普通C编译器)将所有代码编译为开发人员机器上的可执行代码。 因此,编译器将基于某些假设执行优化。 它可能会编译得更慢并进行更多的优化,因为它不会使程序对用户的执行速度变慢。


为什么不将编译后的代码存储在用户计算机的某个地方,这样下次运行应用程序时,JIT就不必再次重新编译它们呢? - omerfarukdogan
好的观察。这是可能的,但它是否实际有益取决于平台和应用程序的使用。JIT优化不一定与离线或提前优化相同,因此好处可能仅仅是“不进行JIT编译”,这可能有所帮助,也可能没有太大帮助。 - Brian Lyttle

9
在Java编译器生成字节码(与体系结构无关)后,执行将由JVM(在Java中)处理。装载程序将字节码加载到JVM中,然后解释每个字节指令。
当我们需要多次调用一个方法时,我们需要多次解释相同的代码,这可能比需要的时间更长。因此,我们有了JIT(即时)编译器。当字节码被加载到JVM(其运行时)中时,整个代码将被编译而不是解释,从而节省时间。
JIT编译器仅在运行时工作,因此我们没有任何二进制输出。

2
当代码被加载到JVM中时,并不会编译整个代码,因为缺乏关于如何进行编译的信息(即指南)。请记住,性能是最终目标。JIT非常有选择性:监视并选择最流行的方法进行优化。它会一直这样做,直到为每个单独的方法达到最大的优化水平。 - Yaw Boakye

7
一个即时编译器(JIT)是一种软件,它接收一个非可执行的输入并返回适当的机器码以便执行。例如:
Intermediate representation    JIT    Native machine code for the current CPU architecture

     Java bytecode            --->        machine code
     Javascript (run with V8) --->        machine code

这意味着对于某个CPU架构,必须安装适当的JIT编译器作为后果。
编译器、解释器和JIT的区别
虽然可能会有例外情况,但通常在将源代码转换为机器代码时,我们可以使用以下方法:
1. 编译器:接收源代码并返回可执行文件。 2. 解释器:逐条执行程序。它获取源代码的可执行段,并将该段转换为机器指令。此过程重复进行,直至所有源代码均转换为机器指令并得以执行完毕。 3. JIT:JIT有许多不同的实现方式,但通常是编译器和解释器的组合。JIT首先通过解释将收到的中间数据(如Java字节码)转换为机器语言。JIT能够经常测量代码的某个部分是否被频繁执行,并将其编译以加快执行速度。

6

即时编译器 (JIT) :
它将 Java 字节码编译成特定 CPU 的机器指令。

例如,如果我们在 Java 代码中有一个循环语句:

while(i<10){
    // ...
    a=a+i;
    // ...
 }

如果i的值为0,则上述循环代码运行10次。
不必一遍又一遍地编译字节码,因为同一条指令将执行10次。在这种情况下,只需要编译该代码一次,然后可以更改该值以达到所需的次数。因此,即时编译器(JIT)跟踪此类语句和方法(如上所述),并将这些字节码片段编译成机器代码以提高性能。
另一个类似的例子是,在字符串/句子列表中使用“正则表达式”搜索模式。
JIT编译器不会将所有代码都编译为机器代码。它在运行时编译具有相似模式的代码。
请参阅Oracle documentation on Understand JIT以了解更多信息。

不需要反复编译字节码10次,因为相同的指令将执行10次。常规编译器呢?它会多次编译这段代码吗? - TT_ stands with Russia

4

我知道这是一个旧的线程,但运行时优化是JIT编译的另一个重要部分,似乎在这里没有被讨论。基本上,JIT编译器可以监视程序运行以确定改进执行的方法。然后,在运行时可以实时进行这些更改。请搜索Google JIT优化(javaworld有一篇相当不错的文章)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接