JEP 草案:Java 虚拟机的提前编译


增强 Java 虚拟机,能够加载编译为本机代码的 Java 应用程序和库,以实现更快的启动和基线执行。

Java 应用程序、库(包括 Java 标准库)和任何用 Java 编写的可插入 Java 虚拟机组件(例如任何 JVMCI 编译器)应该能够以分析或优化模式提前编译为本机代码,并且加载到具有高基准性能的匹配 Java 虚拟机中,绕过解释器,并根据其编译模式完全进行 C1 或 C2 编译。

当前问题
Java 应用程序和库当前在可定制的三阶段模型中执行,首先在解释器中的第 0 层执行,在第 3 层作为 C1 编译代码执行,在第 4 层作为 C2 编译代码执行,第 1 层和第 2 层是特殊情况下的 C1 编译代码代码。

这个过程是非常动态的,涉及代码生命周期中优化和反优化循环的多次迭代。此外,这还意味着代码预热(执行编译高度优化版本的代码工具)可能需要很长时间,从而导致应用程序出现性能问题,其中一些问题可能很严重,这意味着此类问题是不可接受的。

一个很好的例子是 Graal 编译器,它在 Java 17 之前曾是 C2 的可用替代品。编译器启动时的引导阶段对整个应用程序产生了负面影响,因为 Graal 本身必须经过编译才能合理运行,这抵消了它可能带来的显着性能提升。

此外,当 C2 编译的代码遇到不正确的假设时发生的去优化过程也可能代价高昂,因为当这种情况发生时,分析 C1 变体必须再次重新编译,只有在 C2 重新编译相同的方法时才会再次被丢弃。这与上述问题相结合可能会造成浪费。

一个好的解决方案是让该方法的永久提前 C1 编译变体替换解释器和所有运行时 C1 编译,这样在启动时,执行从预编译的 C1 代码开始,然后立即进行到 C2 编译,绕过一些热身步骤,以及解释器。

这也意味着优化可以立即返回到 C1 编译后的代码,而不是一直返回到解释器,而且一旦安装了 C2 变体,C1 nmethods 就不会被丢弃,而是在发生优化时作为优化目标保留下来。

随着 Galahad 再次为 JIT 编译器重新引入潜在的提前编译功能,它计划集成到 JDK 中,考虑这样的功能也是有利的,因为 Galahad 拥有一个现成的平台来将预编译编译器加载到 VM 中。