在本文中,我们将回顾 GraalVM 是什么、它是如何工作的,以及即时 (JIT) 编译和提前 (AOT) 编译之间的区别?
GraalVM 是一种高性能运行时,为用 Java 和其他语言编写的应用程序提供了显着的优势。它由 Oracle 开发,通过利用高级优化和独特的架构,可以更快、更高效地运行应用程序。
1.GraalVM 的历史
1.1.起源
GraalVM 项目于 2011 年在 Oracle 实验室启动,作为优化 Java 编译器研究的一部分。
目标是创建一个新的高性能 JIT 编译器,称为 Graal 编译器,它可以取代 JVM 中现有的 HotSpot C2 编译器。
1.2.里程碑
- 2012--2013 年:最初的开发重点是创建一个可以展示显著性能改进的研究型编译器。
- 2014 年:Graal 编译器首次公开发布,主要针对对 JVM 性能增强感兴趣的研究人员和早期采用者。
- 2017 年:推出 Truffle 框架,通过提供一种在 JVM 之上实现语言解释器的有效方法,允许 GraalVM 执行多种语言的代码。
- 2018 年:GraalVM 1.0 正式发布,其中包括对多种语言和本机图像功能的支持。这标志着一个重要的里程碑,因为 GraalVM 可供一般用户使用。
- 2019--2020 年:继续增强性能、语言支持和开发人员工具。随着越来越多的公司因其性能优势和多语言能力而采用 GraalVM,GraalVM 在行业中获得了关注。
- 2021 年至今:进一步优化和稳定性改进,扩大对更多语言和框架的支持。 GraalVM 周围的社区不断发展壮大,为其生态系统和在云计算、微服务和企业应用等各个领域的应用做出了贡献。
2.GraalVM 是多语言虚拟机
GraalVM 是一个多语言虚拟机,这意味着它可以运行用多种编程语言编写的应用程序。通常,当所有指南都与 GraalVM 相关时,它很有可能是根据大量事实匆忙拼凑起来的构造。最有可能是这样的:
- GraalVM 是一个 Java 虚拟机 (JVM),它可以运行支持 Oracle 的 Java(字节)代码,并提供运行基于 JVM 的语言(如 Kotlin、Scala、Groovy)的可能性。
- 框架 Truffle,GraalVM 不仅可以运行 Java,还可以运行 JS、Python、Ruby、R 和一大堆其他基于 LLVM 的语言(如 C、C++、Rust)。
- GraalVM 支持原生镜像(Native Image),这些镜像由 Graal 编译器提前(AOT)创建。
- Graal 编译器是一个即时(JIT)编译器。
3.GraalVM 的关键特性
- 高性能 JIT 编译器:GraalVM 包含一个即时 (JIT) 编译器,通过在运行时将字节码动态编译为优化的机器代码来提高 Java 应用程序的性能。
- 本机映像:此功能允许将 Java 应用程序提前 (AOT) 编译为本机可执行文件。与传统的基于 JVM 的应用程序相比,这些本机可执行文件启动速度更快,占用的内存更少。
- 多语言功能:GraalVM 可实现不同编程语言之间的无缝互操作性,使开发人员能够在单个应用程序中使用每种语言的最佳功能。
- 工具支持:GraalVM 与各种开发工具和框架集成,增强了调试、监控和分析功能。
4.其他编译器
4.1.什么是 javac?
默认的 Java 编译器 javac 会将您的 Java 源代码(.java 文件)转换为 Java 字节码,生成类文件(.class 文件)。这些类文件可以在安装了 Java 虚拟机 (JVM) 的任何机器上执行。
4.2.什么是字节码?
当您尝试运行 Java 类或应用程序时,您会注意到先前生成的字节码尚未编译为机器码。JVM 使用TemplateInterpreter (模版解释器)解释此字节码。
4.3.什么是模板解释器?
TemplateInterpreter(模版解释器) 逐个处理每个字节码指令,并根据您所使用的特定操作系统和体系结构确定必要的操作。
4.4.什么是JIT 编译器?
JVM 不会无休止地解释您的字节码。相反,它会识别经常执行的代码(称为热路径),并直接将该字节码编译为机器代码。此过程涉及即时 (JIT) 编译器,它通过将热路径转换为机器代码来优化性能。
如果你感兴趣,可以去探索 Java C1 和 C2 编译器以及分层编译的概念。
4.5.JIT 编译器优化
通过广泛的静态代码分析和执行分析,JIT 编译器可以生成针对您的特定平台进行精细优化的机器代码。
4.6.JIT的优点
"一次编写,随处运行" --- 只要安装了 JVM,这一直是 Java 的基本承诺。经过初始的解释预热期后,Java 可通过即时 (JIT) 编译提供良好的运行时性能。
4.7.提前 (AOT) 编译器
我们现在知道什么是 JIT。我们可以继续了解什么是可对抗编译类型,即 AOT 编译。
提前 (AOT) 编译器在应用程序运行之前(而不是在运行时)将 Java 字节码转换为本机机器代码。此过程涉及在构建阶段编译应用程序,生成针对特定操作系统和体系结构定制的可执行二进制文件。
AOT 编译器获取 Java 源代码并将其编译为字节码。然后,它将该字节码转换为本机机器代码。最后,它生成一个可执行二进制文件,可直接在目标平台上运行。
本质上,AOT 编译器将执行大量静态代码分析(在构建时,而不是运行时/JIT),然后为特定平台创建本机可执行文件:Windows、Mac、Linux、x64、ARM 等。等等。 --- 例如,如果您收到 .exe。这意味着您避免在程序启动后进行字节码解释或编译,从而以最快的速度启动应用程序。但是,您必须为要运行程序的每种平台和架构组合创建特定的可执行文件,并且还存在我们稍后将讨论的其他限制。从本质上讲,这种方法违背了 Java 的核心承诺"一次编写,随处运行"。
4.8.AOT 编译的优点
由于代码已经编译,应用程序几乎可以立即启动。AOT 编译的应用程序通常使用更少的内存,因为不需要 JIT 编译器和运行时优化。编译器可以针对目标平台执行大量优化。由于没有 JIT 编译开销,性能更加一致且可预测。AOT 编译可以通过消除运行时代码生成的需求来帮助减少攻击面。
总体而言,AOT 编译为启动时间、内存使用量和一致性能至关重要的环境提供了显著的优势。
5.GraalVM的使用
GraalVM 支持两种代码执行方法:即时 (JIT) 和提前 (AOT) 编译。
JIT 编译通过在运行时将频繁执行的字节码动态编译为优化的机器代码来提高性能,从而平衡启动时间和峰值性能,旨在取代 C2 编译器。相比之下,AOT(本机映像)编译在执行之前将字节码转换为本机机器代码,从而以特定于平台的可执行文件为代价,缩短启动时间并降低内存使用率。这些方法共同为开发人员提供了灵活而强大的选项,用于在各种用例和环境中优化 Java 应用程序。
5.1.在哪里可以使用 Native Image?
当编译并执行为本机可执行文件时,应用程序可以在几毫秒内启动。这种快速的启动时间使本机映像特别适合 Lambdas 或 CLI 应用程序等用例。在这些情况下,项目的规模通常会减轻与预先 (AOT) 编译相关的潜在限制。
5.2.GraalVM 封闭世界假设的局限性
让我们来谈谈 CWA。GraalVM 中的封闭世界假设 (CWA) 指的是在编译时需要知道整个程序及其依赖项。此假设可以在提前 (AOT) 编译期间进行更积极的优化,因为编译器可以做出精确的决策,而无需考虑运行时类或方法的动态加载。
换句话说,CWA 限制了我们的程序。这意味着我们需要在运行之前编译每个代码。因此 Java 反射、动态代理和所有这类东西都停止工作。
5.2.1.封闭世界假设的关键点
- 整体程序分析:AOT 编译器会分析整个程序及其所有依赖项,从而实现彻底的优化。
- 无需动态类加载:由于编译器假设所有代码都是已知的,因此无需处理动态类加载,从而生成更简单、更高效的代码。
- 优化的可执行文件:生成的可执行文件经过高度性能优化,因为编译器可以执行在更动态的环境中无法实现的高级优化。
- 特定于平台:可执行文件针对特定平台和架构进行定制,确保最佳性能,但每个目标环境都需要单独的二进制文件。
- 减少开销:无需运行时解释或 JIT 编译,应用程序可以实现更快的启动时间并减少内存使用量。
5.2.2.优点
- 启动时间更快:预编译代码消除了运行时编译的需要。
- 内存使用率更低:无需 JIT 编译器或运行时优化数据结构。
- 性能更佳:积极优化可实现高效的可执行文件。
5.2.3.评估
- 平台特异性:每个目标环境都需要单独的可执行文件。
- 灵活性较低:无法在运行时加载新类或方法限制了动态功能。
- 构建复杂性:更复杂的构建过程以确保包含所有代码和依赖项。
GraalVM 的封闭世界假设允许通过 AOT 编译显著提高性能,使其成为启动时间和内存效率至关重要的场景的理想选择。
5.3.GraalVM Spring 限制
Spring 应用程序的主要限制,主要与 Spring 框架中常用的反射和动态代理的使用有关。
5.3.1.反射限制
- 动态 Bean 加载:Spring 的动态 Bean 加载机制严重依赖反射,这对于原生镜像编译来说可能存在问题。Spring 的 Bean 实例化过程所需的反射访问可能在原生镜像环境中得不到完全支持或优化。
- 配置元数据:Spring 依赖反射来解析和处理配置元数据,例如注释和 XML 描述符。虽然 GraalVM 的原生镜像支持反射配置,但要实现最佳性能和兼容性可能需要额外的配置和定制。
5.3.2.动态代理限制
- AOP 和基于代理的机制:Spring 的面向方面编程 (AOP) 和基于代理的横切关注点机制依赖于动态代理。GraalVM 的本机映像编译限制了动态代理生成,影响了 Spring AOP 功能的功能和性能。
- 事务和安全代理:用于声明式事务管理和安全实施的 Spring 事务和安全代理在编译为本机映像时可能会遇到限制。这些代理的动态特性与 GraalVM 的提前编译模型相冲突。
5.4.示例
让我们看一个如何使用 SpringBoot 的标准示例。您经常会运行 SpringBoot 应用程序,根据您在启动时设置的特定属性或配置文件(如参数),您可以在运行时加载不同的 bean。
5.4.1.问题
例如,AutoConfiguration 仅在应用程序启动时设置了某个属性(ConditionalOnProperties)时才会创建一个 bean。
对于这种情况,GraalVM 编译器不知道将加载哪个参数。
因此 SpringBoot 根本不支持其原生镜像的 @Profiles 和 @ConditionalOnProperties。其他机制也是如此。
5.4.2.解决方案
- 显式反射配置:Spring 开发人员可以通过为关键类和方法提供显式反射配置来缓解与反射相关的问题。这涉及在本机映像配置中指定反射元数据以确保正确的运行时行为。
- 静态分析工具:利用静态分析工具(例如 Spring Native 和 GraalVM 的本机映像代理)可以帮助在开发过程中识别和解决与反射和代理相关的问题。这些工具可以深入了解有问题的代码模式并建议优化 GraalVM 兼容性。
- 替代实现:在某些情况下,开发人员可能需要探索替代实现或设计模式,以最大限度地减少对反射和动态代理的依赖。这种方法可能涉及重构代码以使用编译时检测或静态编织技术。
虽然 Spring 功能丰富的生态系统提高了开发人员的工作效率和应用程序的可扩展性,但要实现与 GraalVM 原生映像编译的无缝兼容,需要仔细考虑反射和动态代理的使用。通过主动配置和优化解决这些限制,开发人员可以利用 Spring 和 GraalVM 的优势来构建高效且性能卓越的应用程序。
GraalVM 代表了虚拟机技术的重大进步,为现代应用程序提供了多功能且高性能的运行时环境。它能够在单一平台上运行和与多种语言互操作,再加上先进的编译技术,使其成为开发人员构建高效且可扩展应用程序的强大工具。