为什么使用JIT与编译为机器码时Java更快?

我听说Java必须使用JIT来快速。 与解释相比,这是非常有意义的,但是为什么有人不能生成提前生成Java代码的提前编译器? 我知道的gcj ,但我不认为它的输出通常比热点快,例如。

有什么事情让这种语言变得困难? 我认为这归结于这些事情:

  • reflection
  • 类加载

我错过了什么? 如果我避免这些function,是否有可能将Java代码编译一次到本地机器代码并完成?

任何AOT编译器的真正杀手是:

 Class.forName(...) 

这意味着你不能编写一个涵盖所有 Java程序的AOT编译器,因为只有在运行时才能获得有关程序特性的信息。 但是,你可以在Java的一个子集上做这个,这是我相信gcj做的。

另一个典型的例子是JIT在调用方法中直接调用getX()方法的能力(如果发现这样做是安全的),并在适当的情况下撤销它(即使程序员没有明确帮助,一种方法是最终的。 JIT可以看到,在运行的程序中,给定的方法没有被覆盖,因此在这种情况下可以被视为最终的。 这在下次调用时可能会有所不同。

JIT编译器可以更快,因为机器代码正在生成的机器上执行。 这意味着JIT有最好的可用信息来发出优化的代码。

如果将字节码预编译为机器码,则编译器无法针对目标机器,仅针对生成机器进行优化。

我将在“ 程序devise大师”中贴上詹姆斯·高斯林给出的一个有趣的答案。

那么,我听说它说,有效地,你有两个编译器在Java世界。 你有编译器的Java字节码,然后你有你的JIT,基本上再次重新编译一切。 所有的可怕的优化都在JIT中

詹姆斯:确实如此。 现在我们几乎总是击败真正优秀的C和C ++编译器。 当你进入dynamic编译器时,编译器在最后一刻运行的时候会有两个好处。 一个是你知道你正在运行的芯片组。 很多时候,当人们编译一段C代码时,他们必须编译它以运行在通用x86架构上。 几乎没有你得到的二进制文件特别适合其中任何一个。 您下载最新版本的Mozilla,它将运行在几乎所有的英特尔架构CPU上。 几乎有一个Linux二进制文件。 这是非常通用的,它用GCC编译,这不是一个很好的C编译器。

当HotSpot运行时,它确切地知道你正在运行的芯片组。 它确切知道caching如何工作。 它确切知道内存层次结构如何工作。 它确切地知道所有pipe道互锁在CPU中如何工作。 它知道这个芯片有什么指令集扩展。 它优化了你正在使用的机器。 然后另一半就是它正在运行的应用程序。 它能够有统计数据知道哪些事情是重要的。 它能够embeddedC编译器无法做到的事情。 Java世界中内联的东西是相当了不起的。 然后你就可以用现代化的垃圾收集器来pipe理仓库pipe理。 使用现代垃圾收集器,存储分配速度非常快。

编程大师

Java的JIT编译器也是懒惰和适应性的。

懒惰它只是编译方法,而不是编译整个程序(非常有用,如果你不使用程序的一部分)。 类加载实际上有助于让JIT更快地让它忽略尚未遇到的类。

自适应

作为自适应,它会首先发出机器代码的一个快速和脏的版本,然后只会返回,如果经常使用该方法,则会执行一个通过作业。

最后归结为拥有更多信息可以实现更好的优化。 在这种情况下,JIT拥有关于代码运行的实际机器的更多信息(如Andrew所述),并且它还具有许多在编译期间不可用的运行时信息。

Java在跨虚拟方法边界内联的能力以及执行高效的接口调度需要在编译之前进行运行时分析,换句话说,它需要一个JIT。 由于所有的方法都是虚拟的,并且接口被“无处不在”使用,所以它有很大的不同。

从理论上讲,如果JIT编译器有足够的时间和计算资源可用 ,那么JIT编译器比AOT有优势。 例如,如果您的企业应用程序在具有大量RAM的多处理器服务器上运行数天和数月,则JIT编译器可以比任何AOT编译器生成更好的代码。

现在,如果你有一个桌面应用程序,快速启动和初始响应时间(AOT照射的地方)变得更加重要,而且计算机可能没有足够的资源用于最先进的优化。

如果你有一个资源稀缺的embedded式系统,JIT没有机会对付AOT。

但是,以上都是理论。 实际上,创build这样一个先进的JIT编译器比一个体面的AOT更复杂。 一些实际的证据呢?

JIT可以识别并消除一些只能在运​​行时才知道的条件。 一个主要的例子是消除现代虚拟机使用的虚拟调用 – 例如,当JVM发现invokeinterfaceinvokeinterface指令时,如果只有一个类覆盖被调用的方法,则虚拟机实际上可以使该虚拟调用为静态,内联它。 另一方面,对于一个C程序来说,一个函数指针总是一个函数指针,并且对它的调用不能被内联(在一般情况下,无论如何)。

以下是JVM能够内联虚拟呼叫的情况:

 interface I { I INSTANCE = Boolean.getBoolean("someCondition")? new A() : new B(); void doIt(); } class A implements I { void doIt(){ ... } } class B implements I { void doIt(){ ... } } // later... I.INSTANCE.doIt(); 

假设我们不在其他地方创buildAB实例,并且someCondition设置为true ,则JVM知道对doIt()的调用总是意味着A.doIt ,因此可以避免方法表查找,然后将内联呼叫。 在非JIT的环境中类似的构造是不可能的。

我认为官方的Java编译器是一个JIT编译器这个事实是很大的一部分。 花了多less时间优化JVM与Java的机器代码编译器?