Ohhnews

分类导航

$ cd ..
Baeldung原文

Java AOT 缓存优化:JEP 514 与 JEP 515 的改进

#java#jvm#性能优化#aot缓存#openjdk

1. 简介

JDK 24 通过 JEP 483 引入了 AOT(Ahead-of-Time,预先编译)缓存。该缓存允许应用程序通过预加载和预链接类来加快启动速度。然而,创建该缓存的工作流程实际上需要两次独立的 java 命令调用。

因此,JDK 25 通过两项新的 JEP 对此进行了改进。首先,JEP 514 将 AOT 缓存的创建简化为单条命令。其次,JEP 515 将缓存扩展为存储方法执行配置文件,从而缩短了应用程序的预热时间。

在本文中,我们将探讨这两项新的 JEP,并了解它们如何协同工作。需要注意的是,我们需要 JDK 25 才能利用这些特性。

2. JDK 24 中 AOT 缓存的工作方式

在深入了解 JDK 25 的 AOT 缓存方式之前,我们先快速回顾一下 JDK 24 中 AOT 缓存是如何工作的。

当时,为了创建 AOT 缓存,我们必须调用两次 java 工具,从而创建两个独立的 java 进程。第一次调用以记录模式(record mode)运行应用程序。这意味着运行时会观察应用程序在训练运行期间的行为,并将这些信息保存到 AOT 配置文件中:

$ bash
$ java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -cp app.jar com.example.App

第二次调用使用上一步生成的配置来实际构建缓存:

$ bash
$ java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf  -XX:AOTCache=app.aot

最后,我们可以使用生成的缓存运行应用程序,理论上这应该能显著缩短启动时间:

$ bash
$ java -XX:AOTCache=app.aot -cp app.jar com.example.App

尽管这个工作流程确实有效,但它会留下一个临时配置文件,并且需要管理两个独立的命令。经验丰富的读者可能会注意到,AppCDS 归档文件(实际上是 AOT 缓存的前身)也有类似的繁琐两阶段过程。因此,JEP 514 正是为了解决 AOT 缓存的这一问题。

3. 一键式 AOT 缓存创建 (JEP 514)

JEP 514 引入了一个新的非标准 (-XX) 命令行虚拟机选项:AOTCacheOutput。当我们单独使用此选项,而不带任何其他 AOT 标志时,启动器会自动将调用拆分为两个内部子调用——一个用于训练,一个用于缓存创建。

因此,与其使用上述的两步工作流程,我们现在可以简单地执行:

$ bash
$ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App

这条单条命令取代了最初用于创建 AOT 缓存的两条命令。JVM 将应用程序作为训练练习运行,记录动态信息,然后一键完成 AOT 缓存的创建。

生产环境的命令(为生产负载提供缓存)与 JDK 24 保持一致:

$ bash
$ java -XX:AOTCache=app.aot -cp app.jar com.example.App

但需要注意的是——后台仍然执行了两个阶段的过程。实际上,java 启动器创建了两个子进程来完成缓存创建。这一点很重要,并且会带来相应的影响。

4. 一键式方法的缺点

有人可能会认为双进程设置是显而易见的选择,也是最佳实践——事实并非完全如此。

如前所述,这是两个不同的 java 进程,它们都有各自的堆空间。由于它们都是由 java 启动器进程(即通过调用 java 二进制文件创建的进程)启动的,因此峰值内存消耗可能会加倍。所以,如果我们指定 -Xmx4g,那么单步工作流程在完成时,总共可能需要 8GB 的堆内存。

因此,一键式工作流程适用于大多数场景,但两步法也有其用武之地,特别是在内存受限的环境中。托管我们应用程序的云虚拟机很可能没有足够的内存资源来提供双倍大小的堆。在这种情况下,显式的两步工作流程是首选方案。

5. AOT 方法性能分析 (JEP 515)

虽然 JEP 514 简化了 AOT 缓存的创建过程,但 JEP 515 增强了缓存存储的信息内容。

要理解 JEP 515,我们需要回顾 HotSpot 如何达到峰值性能。JIT 编译器识别热点方法(执行频率较高的方法),然后将它们编译为优化的原生代码。但为了做到这一点,它必须为这些方法收集一些性能分析信息。这个过程需要时间。通常,这段时间被称为预热期,在此期间,应用程序的运行速度比其潜在的最佳速度要慢。

坦率地说,应用程序的执行模式往往大致相同。我们的生产负载通常在相同环境下采用相似的 if 分支。因此,性能分析信息在不同的应用程序启动之间不会发生实质性变化。因此,将这些信息预先缓存起来也是有意义的。

因此,JEP 515 通过将训练运行中的方法执行配置文件包含在 AOT 缓存中解决了这个问题。当应用程序在生产环境中启动时,这些配置文件会立即生效,因此 JIT 编译器可以立即开始生成优化代码,而无需等待预热。

最棒的是,我们不需要更改启动命令,更不用说更改应用程序代码,就能从该特性中受益。性能分析数据会在训练运行期间自动收集,然后存储在 AOT 缓存中。因此,它可以通过 JEP 514 的一键式工作流程直接开箱即用:

$ bash
# 训练 + 缓存创建(配置文件自动包含在内)
$ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App
# 生产运行(受益于缓存的类和缓存的性能分析信息)
$ java -XX:AOTCache=app.aot -cp app.jar com.example.App

在上述情况下,JVM 启动时使用的代码缓存已经包含了特定的性能分析信息。

6. 运行时性能分析 vs. 缓存的性能分析

这里需要注意的一点是,缓存的配置文件并不会阻止生产环境中的额外性能分析。HotSpot JVM 会在应用程序运行时继续进行性能分析和优化,结合了 AOT 配置文件、在线性能分析和 JIT 编译的优势。

这一点很重要,因为应用程序在生产环境中的行为仍可能与训练运行期间观察到的行为有所偏离。缓存的配置文件只是给 JIT 提供了一个快速启动,允许它更早地编译某些方法。随着应用程序的运行,HotSpot 会重新评估其对工作负载模式的理解,并在需要时重新编译方法。

7. 结论

JEP 514 和 JEP 515 都是 OpenJDK Project Leyden 计划的一部分,旨在提高 Java 的启动和预热性能。JEP 514 带来了实用的易用性改进——将两步 AOT 缓存创建合并为一条命令。虽然这通常是首选方法,但在内存受限的环境中,两阶段过程可能仍然是更合适的选择。

JEP 515 通过包含方法配置文件丰富了缓存数据,使 JIT 编译器能够从应用程序启动之初就开始进行编译。