我听说Java必须使用JIT才能快速.与解释相比,这是完全合理的,但为什么不能有人制作生成快速Java代码的提前编译器?我知道gcj,但我认为它的输出通常不比Hotspot快.
是否有关于语言的事情使这个困难?我认为这取决于这些事情:
我错过了什么?如果我避免使用这些功能,是否可以将Java代码编译一次到本机机器代码并完成?
And*_*are 37
JIT编译器可以更快,因为机器代码是在它也将执行的确切机器上生成的.这意味着JIT具有可用于发出优化代码的最佳信息.
如果将字节码预编译为机器代码,则编译器无法针对目标机器(仅针对构建机器)进行优化.
Edw*_*rzo 29
我将粘贴一本有趣的答案,由詹姆斯·戈斯林(James Gosling)在"书籍编程大师"中作出.
好吧,我听说它说有效地在Java世界中有两个编译器.你有编译器到Java字节码,然后你有你的JIT,它基本上再次重新编译所有内容.所有可怕的优化都在JIT中.
詹姆斯:没错.这些天我们总是打败真正优秀的C和C++编译器.当你转到动态编译器时,当编译器在最后一刻运行时,你会获得两个优势.一个是你确切知道你正在运行的芯片组.很多时候,当人们编译一段C代码时,他们必须编译它以在通用x86架构上运行.你得到的二进制文件几乎都没有特别适合它们.您下载了Mozilla的最新副本,它几乎可以在任何英特尔架构CPU上运行.几乎有一个Linux二进制文件.它非常通用,它是用GCC编译的,它不是一个非常好的C编译器.
当HotSpot运行时,它确切地知道您正在运行的芯片组.它确切知道缓存的工作原理.它确切地知道内存层次结构如何工作.它确切地知道所有管道互锁在CPU中如何工作.它知道这个芯片有哪些指令集扩展.它可以精确地优化您所使用的机器.然后另一半是它实际上看到应用程序正在运行.它能够获得知道哪些事情很重要的统计数据.它能够内联C编译器永远不会做的事情.在Java世界中内联的东西非常惊人.然后,您将了解存储管理与现代垃圾收集器的工作方式.使用现代垃圾收集器,存储分配非常快.

Luk*_*ane 23
Java的JIT编译器也是懒惰和自适应的.
懒惰它只会在到达它们时编译方法而不是编译整个程序(如果你不使用程序的一部分,这非常有用).类加载实际上允许它忽略它尚未遇到的类,从而有助于使JIT更快.
自适应它首先发出一个快速和脏的机器代码版本,然后只返回并完成一个直接作业,如果经常使用该方法.
Tho*_*sen 23
任何AOT编译器的真正杀手是:
Class.forName(...)
Run Code Online (Sandbox Code Playgroud)
这意味着您无法编写涵盖所有 Java程序的AOT编译器,因为只有在运行时才能获得有关程序特征的信息.但是,您可以在Java的子集上执行此操作,这是我认为gcj所做的.
另一个典型的例子是JIT能够直接在调用方法中内联getX()方法,如果发现它是安全的,并在适当的情况下撤消它,即使程序员没有通过告诉方法是最终的.JIT可以看到在运行程序中,给定的方法没有被覆盖,因此在这种情况下可以被视为final.在下次调用时可能会有所不同.
Tal*_*man 11
最后,它归结为这样一个事实,即拥有更多信息可以实现更好的优化.在这种情况下,JIT有关于运行代码的实际机器的更多信息(如Andrew所提到的),并且它还具有许多在编译期间不可用的运行时信息.
Java能够跨越虚拟方法边界并执行有效的接口调度,这需要在编译之前进行运行时分析 - 换句话说,它需要JIT.由于所有方法都是虚拟的,接口"无处不在",因此它会产生很大的不同.
JIT可以识别并消除一些只能在运行时知道的条件.一个主要的例子是消除现代VM使用的虚拟调用 - 例如,当JVM找到invokevirtual或invokeinterface指令时,如果只加载了一个覆盖调用方法的类,则VM实际上可以使该虚拟调用保持静态,从而能够内联它.另一方面,对于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();
Run Code Online (Sandbox Code Playgroud)
假设我们不在其他地方创建A或B实例并且someCondition设置为true,JVM知道调用doIt()总是意味着A.doIt,因此可以避免方法表查找,然后内联调用.非JITted环境中的类似构造是不可能的.