我们知道,JVM同时使用解释器和JIT编译器。JIT编译器将那些重复的字节码转换成机器码并存储在内存中。现在当解释器逐行翻译并运行字节码时,对于已经转换并存储在内存中的重复代码,它将跳过翻译部分而直接运行它。从而减少并发的冗余翻译。那么为什么Java在JVM中使用解释器呢?像JIT这样的编译器可以一次完成将字节码转换为机器码的整个任务?
lvjbypge1#
在this answer中有一个很好的比较,这让我清楚地知道为什么Java同时使用AOT和JIT。上面提到的here对JIT的“微调”是在JVM的编译器中完成的,它针对运行它的每个系统进行了优化。
lawou6xi2#
事实上,像GraalVM这样的现代Java实现确实提供了将Java字节码的整个类--甚至整个应用程序--编译成本机代码的选项,但这是提前的,而不是在运行时。对于JIT编译器来说,在运行时处理整个类当然是可能实现的,从而完全避免了解释的需要。但是,在GraalVM的本机代码编译器完成了它的工作之后,您有时间享用一顿三道菜的大餐之后,就很容易理解为什么批量预编译 * 不是 * JVM中的默认编译机制了。编译本机代码可能很慢,而且Java本身也不是一种可编译的语言。大多数JVM提供某种形式的自适应解释/编译平衡,在运行时实现。对于重复很多的代码段,解释是“慢”的,因为解释的工作要重复很多次。对于只执行一次的代码,编译是“慢”的,因为编译的工作必须在任何程序指令实际执行之前完成。因此,现代Java实现提供了这两种策略,并试图平衡它们,以获得运行时的最佳整体性能。广义上讲,我们希望(JIT)只编译应用程序中那些编译时间成本最能被其带来的好处抵消的部分。如何做到这一点一直是大量研究的主题,新的方法也在不断开发。
byqmnocz3#
JRockit JVM没有解释器。它编译所有的字节码,即使在进行调试时也是如此,因此没有必要。解释器的一个好处是启动更快。例如,静态初始化器只执行一次,所以通常不需要编译它。
mrfwxfqh4#
并不是所有的JVM实现都使用解释器和JIT编译器的组合。有些只有解释器,有些只有一两个JIT编译器。最著名的JVM HotSpot JVM有两个解释器和JIT两个编译器:
根据您的系统或您配置HotSpot的方式,它将包含C解释器或模板化解释器,这两个解释器不能组合(afaik)。这里我们有第一个优势的解释:它的可移植性更强。2有一个C编译器适用于许多硬件/操作系统的组合。3 JIT编译器产生机器码,因此必须分别移植到每个支持的体系结构。另一个优点是启动时间。JIT编译需要时间,但是解释器可以立即开始执行代码。而且,JIT编译器可以在解释代码的同时在后台运行。JIT编译器不仅可以在解释器运行时进行编译,而且还可以生成跳回到解释器的代码。如果遇到无法编译的内容,它可以生成从编译代码到解释器的跳转,或者甚至可以完全给予编译代码--这很好(在某种意义上),因为它仍然可以被解释。在如此复杂的系统中为某种方法提供确凿的证据是很困难的。一个数据点可能是其他被认为是先进的VM采用了类似的方法,带有解释器和两个编译器:V8和SpiderMonkey JavaScript虚拟机。OP还问为什么不提前编译所有的东西。首先,这可以用GraalVM原生映像来完成,并且已经有了一些其他类似的技术。其次,不提前编译有一些好处:在编译代码之前可以运行代码一段时间,这意味着您可以观察它的行为,并更精确地确定JIT编译器中的优化目标(这个if的else分支从未被执行过--不要浪费它的内联预算)并且因为您可以跳转到解释器,您还可以进行“推测性”优化(甚至不必编译else分支),如果推测失败,代码将跳回解释器。这种方案不需要解释器,只要一个JIT编译器能够从一个编译(更优化)跳到另一个编译(更通用)就可以了。JRockit做到了这一点(afaik)。
qxgroojn5#
解释器允许动态运行和调试代码。编译器必须进行预编译才能运行,这意味着如果您的首选环境没有检测到错误,则需要您进行可能的修复并重新编译整个项目。
因此,它同时具有解释器和编译器,以允许编译器的速度,并仍然允许开发人员能够通过进行小的修改和检查修复,而不必每次重新编译整个项目来进行动态调试。本质上简单地说最大的原因就是这个。
编译器是最终用户运行的最快方式**解释器是开发人员调试和编码的最快方式**
5条答案
按热度按时间lvjbypge1#
在this answer中有一个很好的比较,这让我清楚地知道为什么Java同时使用AOT和JIT。
上面提到的here对JIT的“微调”是在JVM的编译器中完成的,它针对运行它的每个系统进行了优化。
lawou6xi2#
事实上,像GraalVM这样的现代Java实现确实提供了将Java字节码的整个类--甚至整个应用程序--编译成本机代码的选项,但这是提前的,而不是在运行时。对于JIT编译器来说,在运行时处理整个类当然是可能实现的,从而完全避免了解释的需要。
但是,在GraalVM的本机代码编译器完成了它的工作之后,您有时间享用一顿三道菜的大餐之后,就很容易理解为什么批量预编译 * 不是 * JVM中的默认编译机制了。编译本机代码可能很慢,而且Java本身也不是一种可编译的语言。
大多数JVM提供某种形式的自适应解释/编译平衡,在运行时实现。对于重复很多的代码段,解释是“慢”的,因为解释的工作要重复很多次。对于只执行一次的代码,编译是“慢”的,因为编译的工作必须在任何程序指令实际执行之前完成。
因此,现代Java实现提供了这两种策略,并试图平衡它们,以获得运行时的最佳整体性能。广义上讲,我们希望(JIT)只编译应用程序中那些编译时间成本最能被其带来的好处抵消的部分。如何做到这一点一直是大量研究的主题,新的方法也在不断开发。
byqmnocz3#
JRockit JVM没有解释器。它编译所有的字节码,即使在进行调试时也是如此,因此没有必要。
解释器的一个好处是启动更快。例如,静态初始化器只执行一次,所以通常不需要编译它。
mrfwxfqh4#
并不是所有的JVM实现都使用解释器和JIT编译器的组合。有些只有解释器,有些只有一两个JIT编译器。
最著名的JVM HotSpot JVM有两个解释器和JIT两个编译器:
根据您的系统或您配置HotSpot的方式,它将包含C解释器或模板化解释器,这两个解释器不能组合(afaik)。
这里我们有第一个优势的解释:它的可移植性更强。2有一个C编译器适用于许多硬件/操作系统的组合。3 JIT编译器产生机器码,因此必须分别移植到每个支持的体系结构。
另一个优点是启动时间。JIT编译需要时间,但是解释器可以立即开始执行代码。而且,JIT编译器可以在解释代码的同时在后台运行。
JIT编译器不仅可以在解释器运行时进行编译,而且还可以生成跳回到解释器的代码。如果遇到无法编译的内容,它可以生成从编译代码到解释器的跳转,或者甚至可以完全给予编译代码--这很好(在某种意义上),因为它仍然可以被解释。
在如此复杂的系统中为某种方法提供确凿的证据是很困难的。一个数据点可能是其他被认为是先进的VM采用了类似的方法,带有解释器和两个编译器:V8和SpiderMonkey JavaScript虚拟机。
OP还问为什么不提前编译所有的东西。首先,这可以用GraalVM原生映像来完成,并且已经有了一些其他类似的技术。其次,不提前编译有一些好处:在编译代码之前可以运行代码一段时间,这意味着您可以观察它的行为,并更精确地确定JIT编译器中的优化目标(这个if的else分支从未被执行过--不要浪费它的内联预算)并且因为您可以跳转到解释器,您还可以进行“推测性”优化(甚至不必编译else分支),如果推测失败,代码将跳回解释器。这种方案不需要解释器,只要一个JIT编译器能够从一个编译(更优化)跳到另一个编译(更通用)就可以了。JRockit做到了这一点(afaik)。
qxgroojn5#
解释器允许动态运行和调试代码。编译器必须进行预编译才能运行,这意味着如果您的首选环境没有检测到错误,则需要您进行可能的修复并重新编译整个项目。
因此,它同时具有解释器和编译器,以允许编译器的速度,并仍然允许开发人员能够通过进行小的修改和检查修复,而不必每次重新编译整个项目来进行动态调试。
本质上简单地说最大的原因就是这个。
编译器是最终用户运行的最快方式**
解释器是开发人员调试和编码的最快方式**