Azul 开发了不同的解决方案来帮助实现更快的 Java 应用程序预热,其中包括检查点协调恢复 (CRaC) 和 ReadyNow。虽然 CRaC 和 ReadyNow 旨在解决相同的挑战,但它们采用了不同的方法。这篇文章将解释它们的不同之处。
Java 系统面临的挑战之一是应用程序预热。
Java 是许多应用程序的心脏。从单台机器上的小型业务流程到处理大量数据和事务的大规模集群,Java 驱动着无数的应用程序。Azul 提供性能优化,特别是在复杂设置和时间敏感的用例中进行 Java 预热时。
Java 系统面临的挑战之一是应用程序“预热”。Java 应用程序从 Java 字节代码(来自 JAR 文件)开始,将其转换为最适合其运行系统的本地代码。这意味着您的应用程序从一开始就能按预期执行任务,而在了解哪些代码使用频率最高以及如何使用这些代码之后,它的性能会变得更好、速度会变得更快。
Azul 开发了多种不同的解决方案,旨在助力应用程序凭借 Java 预热更快地达到尽可能高的性能水平。
- 检查点协调恢复 (CRaC):创建检查点并从检查点恢复。
- ReadyNow:存储编译器决策,以便在下次运行中重复使用这些决策。
- 云原生编译器:将编译卸载到中心服务。
虽然 CRaC 和 ReadyNow 旨在解决相同的挑战,但它们采用了不同的方法。在这篇文章中,我将解释它们的不同之处。
Java 启动时会发生什么
Java 应用程序启动涉及几个不同的任务:
- JVM(java 程序)本身需要启动。
- JVM 需要加载应用程序类(从 jar 中)、初始化所有资源并启动应用程序逻辑。
- 执行代码并针对运行的机器进行优化。
任务 1 和任务 2 的总持续时间称为“首次响应时间”。此时,应用程序会回复 API 调用、处理传入消息并执行其他重要功能。
在步骤 3 中,一旦代码被编译成本机代码(这一过程称为“预热”),我们就会看到性能有所提升。

Java 预热阶段
Java 应用程序在开始执行代码时会经历三个阶段,然后进入预热:
- Java 虚拟机 (JVM)“按原样”执行 JAR 中的字节代码(类文件)。
- JVM 会跟踪每个方法的使用次数。一旦达到阈值,第 1 层编译器就会尽快将字节代码转换为本地代码。
- 与此同时,JVM 会持续跟踪代码的使用情况。例如,switch 中可能有一些 case 从未使用过。基于这些知识,在经过另一个阈值后,代码会再次转换为本地代码,从而为其运行的系统带来最佳的本地代码。这是由第 2 层编译器完成的。
Java 预热阶段结束后,我们将进入“全速运行阶段”。

去优化
第 2 层编译器会根据代码的使用方式做出某些决策。但是,如果其中某些决策是错误的,编译后的代码就会被去优化,并再次经历编译步骤。在我们之前使用的示例中,其中一个未编译的 case 可能会变得必要,例如,当应用程序开始处理不同的数据时。这可能会导致轻微的性能下降,因为在重新编译之前,执行会退回到字节代码。

这种影响微乎其微,在大多数情况下甚至无法察觉。但是,在某些时间把控至关重要的行业,例如金融交易领域,无论如何都应该避免出现此类去优化的情况。
Azul 提供的解决方案
作为 100% 专注于 Java 和 JVM 的最大公司,Azul 开发了多种解决方案,助力 Java 应用程序加速至最佳运行速度。
检查点协调恢复 (CRaC)
检查点协调恢复 (CRaC) 是在 OpenJDK 的 Azul Zulu 构建版本 (Zulu) 中实施的 OpenJDK 项目。应用程序运行并预热后,您可以指示它创建检查点。这种检查点包含应用程序状态的”转储”。您可以使用该检查点重新启动应用程序,使其恢复到创建检查点时的状态,而无需再次进行 Java 预热。
要创建检查点,应用程序首先需要关闭任何打开的(文件、数据库、套接字等)连接。然后该应用程序可以创建检查点,之后其会被终止。一些框架(如 Quarkus、SpringBoot、Micronaut 和 AWS Lambdas)集成了 CRaC,以简化这一过程。
检查点可以在相同或其他具有相同架构的机器上重新启动。恢复后,它需要重新打开这些连接,但几乎可以立即以与上次运行相同的性能运行。
要使用 CRaC 系统,您需要修改代码以处理连接的关闭和重新打开。
CRaC 通过存储运行中应用程序的完整检查点,消除了 Java 预热阶段。该检查点可在后续运行中快速恢复到相同状态。
ReadyNow
ReadyNow 是 OpenJDK 的 Azul Zing 构建版本 (Zing) 中的一项功能,它是一个基于 OpenJDK 并具有扩展功能、符合 TCK 标准的现代 Java 平台。ReadyNow 可帮助编译器从应用程序启动时直接生成最佳的本地代码。ReadyNow 面向 Azul 高性能 OpenJDK 发行版 Azul Platform Prime 的客户提供。
Zing 的一个显著优势是,每个编译步骤都可以保存在日志文件中,称为配置文件。工程师可以使用这些配置文件来研究特定问题,使用工具(如 GC 日志分析器)对其进行分析,最重要的是,可以在应用程序的下一次运行中重复使用这些配置文件,以便立即将代码编译成性能最佳的本地版本。每次启动应用程序时,Zing 都可以通过命令行选项将配置文件作为输入进行配置,也可以选择配置新的输出配置文件。ReadyNow 会指示 JVM 需要编译哪些代码,并提供其在先前运行中的使用情况信息。这样,就可以创建多个迭代版本的配置文件,以最终获得完美的“成熟”配置文件。此配置文件将包含从一开始就生成最佳本地代码所需的全部信息,并避免可能导致短期性能下降的去优化。
由于此功能已集成到 Zing 中,因此使用 ReadyNow 无需更改代码!您只需提供几个命令行选项即可访问此功能。
由于 ReadyNow 会记录应用程序整个生命周期中的编译决策,因此它可以帮助 JVM 生成经过优化的代码,这些代码能够处理上次运行中出现的所有流量,且不会出现任何去优化的情况。正因如此,它可以避免由此类去优化操作所导致的所有潜在的细微延迟。值得注意的是,创建配置文件的训练运行时间越长,后续运行的结果就越好。
对于大规模部署,Azul 提供额外的 Optimizer Hub 服务,可将 ReadyNow 配置文件的存储和编译卸载到专用环境。
ReadyNow 通过在文本文件中保存上一次运行的编译器决策历史记录,消除了 Java 预热阶段。因此,它能在应用程序启动时立即创建最佳优化代码。它还能避免所有的去优化情况,因为它能根据之前的运行情况准确了解代码的使用方式。
云原生编译器
云原生编译器是 Azul Platform Prime 中 Optimizer Hub 的一个组件,可提供服务器端优化解决方案,将 JIT 编译卸载到单独的专用资源。这种方法可为 JIT 编译提供更强的处理能力,同时将客户端 JVM 从本地进行 JIT 编译的负担中解放出来。
因此,它与本文中提到的挑战有关,但也是对 ReadyNow 的补充。
CRaC 与 ReadyNow 的对比
让我们总结一下 CRaC 和 ReadyNow 之间的差异:
CRaC | ReadyNow | |
---|---|---|
可用版本 | Zulu | Zing |
需要更改代码 | 是,或必须基于支持 CRaC 的框架(Spring、Micronaut、Quarkus …)(*) (**) | 否 |
所需空间 | 应用程序堆大小 + 额外存储空间 | 大小有限的文本文件 |
复杂性 | 调试困难 | 通过 GC 日志文件和配置文件文本文件提供扩展调试选项 |
可处理 Java 模式 (***) | 是 | 是 |
可用性 | 在免费版本中提供基本功能。 Azul 客户可使用扩展功能。 |
针对 Azul 客户 |
(*):即使在使用框架时,您仍可能有需要更改代码的依赖项。
(**):框架中的某些 CRaC 实现会在“首次事务处理时间”时(即调用 main 方法时)获取快照。因此,虽然使用这种方法能够缩短启动时间,但仍无法达到最佳的“全速运行时间”。
(***):与本地编译应用程序(使用 GraalVM)不同,CRaC 和 ReadyNow 仍保留了原始字节代码,以便在需要时进一步优化代码,还可以动态加载类等。
结语
尽管 CRaC 和 ReadyNow 都是缩短 Java 应用程序预热阶段的解决方案,但它们采用的方法却截然不同。在无法更改代码、服务器系统需要尽快可用以及需要提高性能和/或降低总体拥有成本的情况下,ReadyNow 是提升 Java 性能的最佳选择!
在我们的文档和网站上了解有关 CRaC 和 ReadyNow 的更多信息。请关注 Azul 博客,以查阅有关 ReadyNow 功能的更多文章。
下一篇:了解 ReadyNow 如何缩短预热时间