2

什么是GraalVM、AOT 和 JIT?

 6 months ago
source link: https://www.jdon.com/72604.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

什么是GraalVM、AOT 和 JIT?

用本指南来了解 GraalVM 是什么、它的工作原理以及即时 (JIT) 编译与提前 (AOT) 编译的比较。

如果 Graal 的本机可执行文件几乎立即启动、更小并且消耗更少的资源 , 为什么您会想在 Java/JVM 项目中使用其他的呢?

为了给您一个正确的答案,我们必须快速浏览一下 Java 的编译器环境。与往常一样,我们将从最基础的开始。

什么是 Javac?
默认的 java 编译器称为javac,它采用您的 Java 源代码(您的 .java 文件),如下所示:

//...public static void main etc

public static int add(int a, int b) {
  return a + b;
}

并将其转化为 Java 字节码,即你的类文件,如 Main.class。您可以在任何安装了 JVM 的机器上运行这些文件。

这就是上述方法的字节码(通过 javap -c Main.class 命令生成):
0: iload_0
1: iload_1
2: iadd
3: ireturn

字节码会发生什么变化?
当您尝试运行 Java 类/应用程序(例如 java Main.class)时,上面的字节码尚未编译成机器代码,因此 JVM 需要解释字节码。为此,JVM 需要使用模板解释器(TemplateInterpreter),如果您对此感兴趣,可以阅读此处。

模板解释器的作用是什么?
简单来说:可以把它想象成逐条查看上面的语句(例如 istore_1),并找出在当前运行的特定操作系统和架构下需要执行的语句。

什么是 JIT 编译器?
然而,JVM 是聪明的,它并不只想无休止地解释字节码。它还会记住程序经常执行的代码(所谓的热路径),然后直接将字节码编译成机器码(好奇者可查阅 Java 的 C1 和 C2 编译器以及分层编译)。

通过大量的静态代码分析和运行时信息,JIT 编译器可以输出特定平台的优化机器代码。

JIT 方法有哪些优势?
短小精悍:

  • Java 最初的承诺:一次编写,随处运行(安装 JVM)。
  • 经过预热解释期后,在运行时获得 "卓越 "性能 → "Just In Time"(及时)。

什么是 AOT 编译器?
之前的JIT路径是:
.java -> javac -> bytecode -> jvm -> interpreter -> JIT

AOT 编译器还可以走以下路线:
.java -> AOT magic -> native executable (think .exe / elf)

从本质上讲,AOT 编译器会进行大量静态代码分析(在编译时,而不是运行时/JIT),然后为特定平台创建本地可执行文件:例如,你会得到一个 Main.exe。

这意味着你不必在启动程序后进行字节码解释/编译,而是可以全速启动应用程序。反过来说,你必须为每一个你希望程序运行的平台 x 架构组合创建一个特定的可执行文件(还有其他一大堆限制,我们稍后再谈)。

这基本上与 Java 最初的承诺背道而驰。

什么是 GraalVM?
GraalVM 同时提供 JIT 和 AOT 编译器,但人们可能会错误地将 Graal 的所有功能与其本地镜像功能混为一谈。

  • GraalVM是一个 Java 虚拟机 (JVM),它可以运行 Java(字节)代码并由 Oracle 维护。
  • GraalVM不仅可以运行Java,还可以通过Truffle Framework运行JS、Python、Ruby和其他我忘记的语言。
  • Graal Compiler是一种即时 (JIT) 编译器。
  • 还有Native Image,它是 Graal 的 Ahead-of-Time (AOT) 编译器。

本质上:

  • Graal Compiler (JIT) 本质上是 C2 (JIT) 编译器的替代品。
  • Native Image 是 AOT 编译器,可以为 Java 源文件创建本地可执行文件。

AOT 是否存在问题?
是的,有。

当 Graal 或任何 AOT 编译器创建本地可执行文件时,都需要根据所谓的封闭世界假设进行静态代码分析。实际上,这意味着编译器需要知道在构建时运行时可触及的所有类,否则代码将不会出现在最终的可执行文件中。

这就意味着,所有涉及动态加载的东西,如反射、JNI 或代理(很多 Java 库和项目使用的所有好东西),都是潜在的问题。

public static void main(String[] args) {
  if (isFriday()) {
    String myClass = "com.marcobehler.MyFancyStartupService";
    MyFancyStartupService instance = (MyFancyStartupService)
                                Class.forName(myClass)
                                .getConstructor()
                                .newInstance();
    instance.cashout();
  }
}

静态代码分析不会执行你的代码,因此编译器不知道你的代码是否真的是星期五代码,因此你的 MyFancyStartupService 不会被编译器看到,也不会出现在最终的可执行文件中。

有一些解决方法:在这种情况下,您可以指定 JSON 文件形式的元数据,让 AOT 编译器知道 MyFancyStartupService。这也意味着,您想包含在项目中的任何库都需要 "AOT ready",并在适用时提供此元数据。

Spring案例
根据启动 Spring 应用程序时设置的特定属性或配置文件,运行时加载的 Bean 可能会有所不同。

请看下面的自动配置(AutoConfiguration),它只会在特定属性被设置(例如在应用程序启动时的配置文件中)的情况下创建 FlamegraphProvider Bean。

同样,在构建过程中,Graal 编译器无法知道是否会出现这种情况,因此 Spring (Boot) 完全不支持 @Profiles 和 @ConditionalOnProperties 的本地镜像。

@AutoConfiguration
@ConditionalOnProperty(prefix = "flamegraphs",
                       name = "enabled",
                       havingValue = "true")
public static class FlamegraphConfiguration {

@Bean
  public FlamegraphProvider flamegraphProvider() {
      // ...
  }

}

还有其他潜在问题吗?
有。

  • AOT 编译需要大量资源。就 Spring 的原生映像而言,编译需要占用大量内存和 CPU。不过,CI/CD 提供商会很高兴的!
  • 与创建字节码相比,创建本地可执行文件也需要更多时间。以 Spring Boot 骨架应用程序为例,我们需要花费几分钟(AOT),而不是几秒钟(JIT)。
  • 根据平台的不同(Windows!),设置所有必要的 SDK 和库也非常麻烦,甚至无法启动本地编译。
  • 如果您无法控制目标环境,就像您的传统桌面应用程序一样:最终,您将需要一个疯狂的 CI/CD 矩阵,为各种支持的架构/平台创建本地可执行文件。您还需要支持和维护 CI/CD 矩阵。
  • 例如,将服务器可执行文件放入 Docker 容器中时,这就不是问题了,但稍后再详述。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK