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