我听说Java必须使用JIT才能快速。与解释相比,这是完全合理的,但为什么不能有人制作生成快速Java代码的提前编译器?我知道gcj
,但我不认为它的输出通常比Hotspot快。
是否存在导致这种困难的语言?我认为这归结为这些事情:
我错过了什么?如果我避免使用这些功能,是否可以将Java代码编译一次到本机代码并完成?
答案 0 :(得分:37)
JIT编译器可以更快,因为机器代码是在它也将执行的确切机器上生成的。这意味着JIT具有可用于发出优化代码的最佳信息。
如果您将字节码预编译为机器代码,则编译器无法针对目标计算机进行优化,只能针对构建计算机进行优化。
答案 1 :(得分:29)
我将在书籍James Gosling中粘贴Masterminds of Programming给出的有趣答案。
嗯,我听说过这么说 实际上你有两个编译器 Java世界。你有编译器 到Java字节码,然后你有 你的JIT,基本上重新编译 一切都是特别的。所有的 你可怕的优化是在 JIT 强>
詹姆斯:完全正确。这些天我们都是 打败真正优秀的C和C ++ 编译器几乎总是。当你 转到动态编译器,你得到 编译器的两个优点 在最后一刻跑。一 你知道什么是芯片组吗? 你正在奔跑。这么多次 人们正在编译一块C 代码,他们必须编译它才能运行 关于通用x86的种类 建筑。几乎没有 你得到的二进制文件特别好 调整了他们中的任何一个。你下载了 Mozilla的最新版本,它会 几乎任何英特尔都可以运行 架构CPU。有很多 一个Linux二进制文件这很通用, 它是用GCC编译的 不是一个非常好的C编译器。
当HotSpot运行时,它确切地知道 你在运行什么芯片组。它 确切地知道缓存是如何工作的。它 确切知道内存层次结构 作品。它确切地知道所有的 管道互锁在CPU中工作。 它知道什么指令集 这个芯片有扩展。它 精确地优化什么机器 轮到你了。然后是另一半 是它实际上看到了 应用程序正在运行。它能够 有统计数据知道哪些 事情很重要。它能够 C编译器可以内联的东西 永远不会做。得到的东西 在Java世界中内联很漂亮 惊人。然后你抓住那个 存储管理的工作方式 现代垃圾收集者。有了 现代垃圾收集器,存储 分配非常快。
答案 2 :(得分:23)
Java的JIT编译器也是懒惰和自适应的。
懒惰它只会在到达它们时编译方法而不是编译整个程序(如果不使用程序的一部分,则非常有用)。类加载实际上允许它忽略它尚未遇到的类,从而有助于使JIT更快。
自适应它会首先发出机器代码的快速和脏版本,然后只返回并执行直接作业,如果频繁使用该方法。
答案 3 :(得分:23)
任何AOT编译器的真正杀手是:
Class.forName(...)
这意味着您无法编写涵盖 ALL Java程序的AOT编译器,因为只有在运行时才能获得有关程序特征的信息。但是,您可以在Java的一个子集上执行此操作,这是我认为gcj所做的。
另一个典型的例子是JIT能够直接在调用方法中内联getX()方法,如果发现它是安全的,并在适当的情况下撤消它,即使程序员没有明确帮助通过告诉方法是最终的。 JIT可以看到在运行程序中,给定的方法没有被覆盖,因此在这种情况下可以被视为final。在下次调用时可能会有所不同。
答案 4 :(得分:11)
最后,它归结为这样一个事实:拥有更多信息可以实现更好的优化。在这种情况下,JIT有关于运行代码的实际机器的更多信息(如Andrew所提到的),并且它还有许多在编译期间不可用的运行时信息。
答案 5 :(得分:6)
Java能够跨越虚拟方法边界并执行有效的接口调度,这需要在编译之前进行运行时分析 - 换句话说,它需要JIT。由于所有方法都是虚拟的,接口“无处不在”使用,因此它会产生很大的不同。
答案 6 :(得分:6)
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();
假设我们不在其他地方创建A
或B
个实例并且someCondition
设置为true
,则JVM知道对{{1}的调用总是意味着doIt()
,因此可以避免方法表查找,然后内联调用。非JITted环境中的类似构造不可能是无法使用的。
答案 7 :(得分:5)
理论上,如果JIT编译器有足够的时间和可用的计算资源,那么它比AOT 具有优势。例如,如果您的企业应用程序在具有大量RAM的多处理器服务器上运行数天和数月,则JIT编译器可以生成比任何AOT编译器更好的代码。
现在,如果您有桌面应用程序,快速启动和初始响应时间(AOT闪耀的地方)等内容变得更加重要,而且计算机可能没有足够的资源进行最高级的优化。
如果你的嵌入式系统资源稀缺,JIT就没有机会反对AOT。
然而,以上都是理论。实际上,创建这样一个高级JIT编译器比一个像样的AOT编译器更复杂。一些 practical evidence 怎么样?
答案 8 :(得分:2)
我认为官方Java编译器是JIT编译器的事实是其中很大一部分。优化JVM与Java的机器代码编译器花了多少时间?
答案 9 :(得分:0)
Dimitry Leskov绝对是在这里。
以上所有内容只是能够使JIT更快的理论,实现每个scenaro几乎是不可能的。此外,由于我们在x86_64 CPU上只有少量不同的指令集,所以通过针对当前CPU上的每个指令集来获得很少的收益。在本机代码中构建性能关键应用程序时,我总是遵循针对x86_64和SSE4.2的规则。 Java的基本结构造成了很多限制,JNI可以帮助您展示它的低效率,JIT只是通过使其整体更快来糖涂层。除了默认情况下每个函数都是虚函数之外,它还在运行时使用类类型,而不是像C ++。在性能方面,C ++具有很大的优势,因为在运行时不需要加载类对象,它是在内存中分配的所有数据块,只在请求时初始化。换句话说,C ++在运行时没有类类型。 Java类是实际对象,而不仅仅是模板。我不打算进入GC因为那是无关紧要的。 Java字符串也较慢,因为它们使用动态字符串池,这需要运行时每次在池表中进行字符串搜索。其中许多事情都是因为Java最初并不是为了快速构建,所以它的基础总是很慢。大多数本地语言(主要是C / C ++)专门构建为精简和平均,不浪费内存或资源。事实上,Java的前几个版本非常缓慢且浪费在内存上,对于变量有很多不必要的元数据,而不是。就像今天一样,JIT能够生成比AOT语言更快的代码仍然是一种理论。
考虑JIT需要跟踪的所有工作来执行惰性JIT,每次调用函数时递增计数器,检查它被调用了多少次......依此类推。运行JIT需要花费很多时间。我眼中的交易不值得。这只是在PC上
曾经尝试在Raspberry和其他嵌入式设备上运行Java吗?绝对可怕的表现。 Raspberry上的JavaFX?甚至没有功能...... Java和它的JIT远远不能满足它所宣传的所有内容以及人们盲目地推出它的理论。