Leyden 项目

您可能听说过 项目 Leyden,这是 OpenJDK 项目中的一项具有宏大目标的倡议。

作为 Quarkus 用户,您可能会想知道这个项目将如何使您受益,以及它与 GraalVM 原生镜像的区别。虽然我们认为公平地说,Leyden 的灵感或至少是动机来自于 GraalVM 原生镜像中首先实现的一些想法,但 Leyden 有着显著的不同。理解它是如何工作的至关重要:正如我们将看到的,Leyden 不是 GraalVM 原生镜像的替代品,而是 JVM 的一个实质性演进,我们预计它也将为原生镜像带来一些好处。

为了尝试澄清这一点,不幸的是,这篇帖子异常的长:我们希望它能成为一个简短的指南,“启用 Leyden 的方法”,但现在还不是时候,因为我们需要先理解不同的模型。有时,术语也不同;例如,“Ahead of Time (AOT)”在 GraalVM 原生镜像的上下文中具有非常特定的含义,并且传统上与“编译”相关联,但在 Leyden 的上下文中,它被更广泛地用于表示 JVM 操作的各种方面;希望阅读完本文后,这会令人困惑的程度有所减轻。

关于 Leyden 的另一个重要的误解是,它是一个“提高启动时间”的项目;这个说法没错,因为提高启动时间是它的目标之一。但该项目提出的其他目标为我们最喜欢的平台 Quarkus 及其用户提供了更大的潜力。

那么,让我们开始吧。

什么是 Leyden?

项目 Leyden 是 OpenJDK 团队的一项倡议。它是一项正在进行的实验,目前由参与该项目的不同公司的团队共同努力开发。

该项目的主要目标是提高 Java 程序的启动时间、达到峰值性能的时间以及应用程序的内存占用。

 — 项目 Leyden,在其项目页面的第一项

Leyden 是一个旨在解决启动缓慢和内存占用大的通用项目。保持 JDK 引导时间和内存占用低是很有用的。这有助于减少能耗、硬件资源使用,并最终降低成本。然而,同样重要的是减少应用程序达到峰值性能的时间,这通常是加载应用程序类和执行应用程序代码(包括 JIT 编译热代码路径的方法)的时间。减少应用程序的内存占用可以产生巨大的影响,这不仅可以通过修剪应用程序数据,还可以通过修剪应用程序类和代码来实现。Leyden 正在解决 JVM 如何帮助开发人员实现这些目标的方法;在许多方面,这与 Quarkus 在框架层面提供的技术相辅相成,因此我们期望它们结合起来能产生强大的结果。

请注意,项目正在快速发展:本文中解释的一些内容在编写本文时也在不断变化。如果您打算在更技术层面参与其中,请关注 Jira 和 Leyden 邮件列表中的开发。

为什么它对 Quarkus 有吸引力

从 Quarkus 的角度来看,我们在所有这些指标上都做得很好,但我们一直在寻找改进。这就是项目 Leyden 引起我们注意的原因。我们已经与 Red Hat 的 OpenJDK 团队的同事们合作,他们直接参与了与更广泛的 OpenJDK 组一起实现 Leyden;今天的这篇博文是不同团队工程师们的合作。

尽管 Quarkus 已经在 Ahead of Time (AOT) 阶段做了大量工作来加快预热和响应时间,但 Leyden 带来的增强更多地与 JVM 的行为方式有关。通过补充这两种方法,Quarkus 和 Leyden 的结合所带来的优势将远远超过单独使用它们所能获得的优势。

由于这种技术协作的潜力巨大,Quarkus 和 OpenJDK 团队正在合作开发各种原型,并且欢迎社区中的任何人加入。

JVM 引导过程回顾

为了更好地理解潜在改进的范围,我们需要退一步,讨论一下 JVM 目前是如何工作的,特别是我们的应用程序是如何启动的,以及它如何从解释字节码演进到其最高性能模式:运行为特定硬件、当天配置和特定工作负载量身定制的高度优化的本地代码。没有任何其他运行时能够媲美 JVM 在这方面的能力。

正如我们所知,Java 运行时并不直接运行 Java 源代码。我们的 JAR 文件内容不是可执行的机器码,而是从 Java 源代码生成的 Java 字节码,通常使用 javac 编译器,但在某些情况下,Quarkus 会生成直接生成的字节码。字节码的一个关键特性是可移植性,它以一种与机器和操作系统无关的格式编码了 Java 类结构及其方法操作。Java 运行时在布局 Java 对象时会遵循字节码中的类型信息。方法执行通常涉及解释方法字节码中的操作,尽管运行时也可以选择将方法字节码编译为等效的本地机器代码并直接执行后者。

字节码的交付单位是类文件,它模拟单个类。Java 运行时本身提供了大量的实用程序和运行时管理类,因为类文件嵌入在系统 JAR 或 jmod 文件中。应用程序通过将 JAR 添加到类路径或模块路径来补充自己的类文件。

字节码是逐类提供的,以便运行时可以 *惰性地* 加载类:即,运行时只会在执行过程中需要该类的定义时才查找、验证和消耗类文件。

惰性加载使得 Java 成为一种动态语言——即程序中包含的代码可以在运行时决定。这可能包括从运行时标识的 JAR 文件中加载类,可能通过网络加载。或者,它可能包括在运行时生成类字节码,就像代理类或服务提供商辅助类所做的那样。

即时编译 (JIT) 和预编译 (AOT)

描述 Java 惰性加载的另一个名称是“即时”(JIT)。JIT 是描述 Java 运行时编译器操作的常用术语。不太为人所知的是,它的用途要广泛得多。JIT 不仅限于编译:JVM 执行的许多其他操作也是在运行时惰性地或“即时”完成的。

做“即时”(JIT) 操作的替代方法是“预编译”(AOT)。例如,GraalVM 的 Native Image 运行时在镜像构建时加载和分析应用程序所需的每一个类的字节码,包括 JDK 运行时类。它使用编码在字节码中的类型和方法信息来“预编译”一个完整的程序,该程序包含应用程序可能执行的每个方法的代码。

GraalVM 原生镜像的方法处于一个极端:一切都在 AOT 中完成,而传统的 Java 运行时模型处于另一个极端,尽可能多地在 JIT 中完成。然而,实际上可以在一个运行时中混合和匹配 AOT 和 JIT 执行模型:重新平衡 AOT 和 JIT 的组合是项目 Leyden 第一个 EA 版本的目标。

有趣的是,Quarkus 也应用了这种时间转移概念;我们称之为“增强”,它基本上是在应用程序的构建时启动流行的框架,以避免在运行时产生性能损失。

原生镜像构建还可以利用 Profile Guided Optimisations (PGO),它允许它利用运行时的一些数据来指导编译过程,指导其优化。这本质上是“窥视未来”——一种时间转移的形式。然而,它只是窥视运行时指标的模拟,最终,编译器仍然需要提前做出所有优化权衡;这有利有弊。主要缺点是任何欠佳的决定都会被固定下来;幸运的是,有一个回退机制可以从完全错误的决定中恢复,但这个机制不能生成新的最佳代码。对于短期应用程序来说,优点更为明显,因为在几乎没有机会利用 JIT 优化支持的情况下,在运行时携带所有 JIT 优化支持的权衡不太合理。

Default Java compilation and run
图 1. 在默认的 Java 编译和运行中,我们有两个不同的阶段:首先我们将源代码编译为字节码。然后我们使用该字节码来运行应用程序。

类数据共享 (CDS) 作为 AOT 缓存的步骤

就 OpenJDK 运行时而言,将工作转移到 AOT 完成并不是一个全新的想法。OpenJDK 多年来一直支持通过 CDS 进行混合 AOT/JIT 类加载模型。导致 类数据共享 (CDS) 被提出的观察是,大多数应用程序在每次运行时都会加载相同的类,包括 JDK 引导期间的 JDK 类以及应用程序启动和预热期间的应用程序类。

加载需要定位类字节码文件,可能需要调用 Java ClassLoader,解析字节码,然后构建 JVM 内部的类模型。这个内部模型将字节码中打包的信息解压成一种格式,以便进行快速解释或编译执行。如果这个加载和解压工作可以一次完成,并且生成的类模型可以在后续运行中高效地重用,那么就可以节省启动和预热时间。

最初,CDS 优化了大量核心 JDK 类的加载。它通过启动 JVM 并将引导期间加载的所有类的类模型转储到内存格式的归档文件中来工作。然后,生成的 JDK 模块、类、字段和方法图可以在下次 JVM 运行时快速重新映射到内存中。加载存档中存在的类涉及对 AOT 类模型的简单查找。加载存档中不存在的类需要进行字节码查找、解析和解压的常规 JIT 步骤,即 CDS 实现了一种混合的 JIT/AOT 执行模型。

Static CDS benefits
图 2. 静态 CDS 归档在 JVM 安装期间构建,并包含核心库的类。此归档可用于在运行应用程序时将部分类加载转移到 AOT。

自 JDK17 以来,每个 JVM 版本都附带了一个默认的 JDK 运行时类的 CDS 归档,使 JDK 启动时间减半。CDS 已得到改进,允许在执行简短的应用程序训练运行后将应用程序类包含在 CDS 归档中。由此产生的混合 AOT/JIT 操作可以显著改善应用程序的启动和预热时间,具体取决于训练运行对应用程序代码的执行程度。因此,选择性的 JIT/AOT 操作并不是什么新鲜事。

Dynamic CDS benefits
图 3. 在进行训练运行时,我们创建一个包含应用程序运行方式信息的归档。此归档不仅包含核心库的类,还包含我们应用程序的类。

Quarkus 使生成特定于应用程序代码的 CDS 归档变得非常容易;这项功能已经存在很多年了:请参阅 Quarkus 中的 AppCDS 指南。随着 Leyden 的到来,我们的目标是进一步发展这一点,并为 Leyden 完全自动化,以便为您带来更多好处,而无需额外的麻烦。

项目 Leyden 的目标是将 AOT 与 JIT 的权衡从类加载(由 CDS 完成)扩展到 JVM 中的其他 JIT 操作;有许多操作可以“转移到 AOT”,例如创建堆对象来表示常量、收集执行配置文件信息等等。最重要的是,它将 AOT 转移到在解释执行期间通常发生的惰性链接,以及当方法被执行足够多次以证明编译成本时发生的惰性编译和重新编译。

AOT 与 JIT 链接

类链接是 JVM 惰性执行的另一项操作。当处理类字节码时,类会直接链接到其所属的模块以及其拥有的方法和字段。JIT 链接将每个独立的、已链接类子图的元素连接起来,形成一个完全连接的图,其中来自不同(类或模块)文件的元素相互交叉引用。

加载和链接需要递归进行。例如,每个类(Object 除外)都需要链接到其超类。没有确保超类已加载,超类链接就无法完成。事实上,如果找不到超类的字节码或其无效(例如,它标识的是一个接口而不是类),则可能发生链接错误。同样,方法字节码中的新操作或字段 get/put 操作只能在加载字节码中命名的类(和字段)之后进行链接。

链接有时是惰性的,但并非总是如此。事实上,为了允许加载也是惰性的,必须以惰性方式进行某些链接,否则,一旦进入主例程,整个类图就会被链接和加载。超类链接总是在子类刚刚加载时就立即执行。这是因为在使用子类创建实例或执行方法时,如果不了解超类的定义,是不可能做到的。相比之下,字段和方法链接是惰性执行的。在这些情况下,链接作为执行的副作用发生。当一个方法第一次执行字段 get/put 或方法调用字节码时,目标字段或方法会通过其所属类进行查找,必要时进行加载。字段类型或方法签名会被检查一致性,并且字段的查找位置或方法的调用方式等详细信息会被缓存,从而允许下次执行字节码时跳过链接步骤。

与惰性加载一样,这种惰性方法会导致几乎完全相同的链接在每次运行时建立。用于停止和重新启动执行以惰性连接类图所花费的时间占 JDK 启动、应用程序启动和应用程序预热(达到峰值运行时间)的显著比例。如果我们能够提前计算此链接并避免在运行时建立它,我们就可以加快启动速度,更重要的是,加快预热时间。

与 Quarkus 的协同作用

类加载和链接是应用程序预热过程中的重要步骤,因为它涉及搜索整个类路径以查找 JVM 将要运行的字节码引用的所有类和对象。默认情况下,这是一个惰性操作,因为加载和链接类路径中的所有现有类不仅会占用更大的内存,还会增加预热时间。这就是为什么 JVM 只编译和链接将要使用的字节码。

Quarkus 通过(除其他策略外)积极减少类路径中包含的类集来加速这一过程,从而加快匹配搜索速度。类搜索也通过 Quarkus 在构建时完全分析应用程序时生成的索引来加速。但这仍然是一个繁重的操作,难以提前执行,因为我们不知道将运行什么以及如何运行。Quarkus 可能能够在未来为链接器提供一些额外的提示。

Leyden 提供的第一个提高启动时间的改进是升级 CDS 项目中开发的 AOT 模型,使其不仅包含类预加载,还包含预链接,如 JEP Ahead-of-Time Class Linking 中所述。

可以在训练运行期间生成 AOT 缓存,该训练运行引导 JVM,并可选地执行应用程序特定的代码。与 CDS 归档一样,AOT 缓存以允许在后续运行时快速重新映射的格式存储训练运行期间加载的所有类的类图。存储的图还包括在训练运行期间执行的代码建立的任何链接信息。预缓存的链接避免了在后续运行时停止和开始执行以执行链接的需要。

Leyden CDS benefits
图 4. Leyden 的 AOT 缓存包含更多预生成的内容,使我们能够将部分加载、链接和编译转移到 AOT,从而实现更快的应用程序启动和预热。

请记住,训练运行使部分加载和链接可以 AOT 完成,但任何未训练的内容仍将通过常规 JIT 过程执行:AOT 方法不必全面应用,以便 JVM 可以回退到常规加载系统来处理无法从 AOT 处理中受益的用例。这种回退到“常规 JIT 处理”的能力是 GraalVM 原生镜像无法使用的优势。

JIT 与 AOT 编译

JVM 执行的另一个众所周知的惰性操作是 JIT(运行时)编译。方法字节码通常被解释,但 JVM 会惰性地将字节码转换为等效的机器码。由于生成最佳机器码是一项昂贵的操作,因此它选择性地执行此编译任务,仅编译那些已被调用多次的方法。

JIT 编译也是“自适应”的,即 JVM 会惰性地重新编译某些方法,使用不同的“层”或编译级别。

  1. Tier 1 编译生成仅轻度优化的代码,基于非常有限的执行配置文件数据。

  2. Tier 2 编译也生成轻度优化的代码,但对其进行插桩以分析控制流。

  3. Tier 3 编译添加了进一步的插桩,该插桩记录了更多关于执行内容(包括值的类型)的详细信息。

  4. Tier 4 编译使用所有收集到的配置文件信息,并执行大量的优化。

Tier 1 - 3 编译省略了许多可能的优化,以便快速交付编译后的代码。Tier 4 编译可能需要更长的时间来完成,因此只尝试为一小部分执行频率很高的方法。

有时,代码会根据从配置文件数据推断出的“推测性”假设进行大量优化。在这种情况下,编译器会做出乐观的假设,即某个条件在未来将始终为真,但会包含一个有效的检查来在执行过程中验证该假设,以确保在猜测最终被证明是错误的情况下程序的语义不受影响;当检测到这种情况时,代码将被去优化,回退到先前的编译级别,并且配置文件数据会被调整,以便最终能够用更好的信息重新编译。本质上,一些代码可能会被多次重新编译,并偶尔回退到较低的级别:这是一个高度动态的过程。

当大部分运行的代码都以最高级别编译,并且后台编译活动变得非常少甚至没有时,就达到了峰值优化。

为达到峰值性能而编译代码也需要相当多的资源,因此提前执行此工作也可以节省宝贵的 CPU 周期,并在应用程序引导期间节省大量的内存:Java 开发人员不习惯衡量 JIT 编译器的内存成本,但它被隐藏的事实并不意味着它不存在;虽然这对大型企业服务器来说可能是一个细节,但在开发微服务或仅以更小、更节能的目标为目标时,意识到这些资源成本非常重要。

但是,仅通过检查字节码,我们能提前优化哪些内容存在一些限制。例如,大量使用反射会阻止编译器预测哪些符号将在运行时加载、链接和最常使用。

Leyden 项目已经成功地将方法编译的工作从 JIT 转移到 AOT。在训练运行期间会跟踪方法的执行和编译。在运行结束时,任何相关的配置文件信息和方法编译后的代码都会保存到 AOT 缓存中,以便在下次运行时能够快速地将其重新映射回内存并重用。

与 AOT 加载和链接一样,训练运行可以使部分配置文件和编译工作 AOT 完成,但允许尚未训练的内容仍通过常规 JIT 编译过程进行编译。请注意,方法代码不需要在最高级别上编译就可以被保存。此外,当恢复以较低级别编译的代码时,仍然可以将其重新编译到更高的级别。

编译后的代码也可以被去优化和重新优化,以适应不同的运行时条件,就像当前运行时中编译的代码一样。因此,AOT 编译的使用已完全集成到 OpenJDK 的自适应、动态编译和重新编译模型中:即使 AOT 编译期间的某些假设被证明不是最佳的,即时编译器也可以在运行时进行干预,并用新信息改进代码。

如何玩转它

第一步是安装一些您可以在 jdk.java.net/leyden/ 上找到的 Leyden 早期版本。

通过运行以下命令确保您已正确安装

$ java --version
openjdk 24-leydenpremain 2025-03-18
OpenJDK Runtime Environment (build 24-leydenpremain+2-8)
OpenJDK 64-Bit Server VM (build 24-leydenpremain+2-8, mixed mode, sharing)

转到您想用 Leyden 测试的应用程序,并启动第一次训练运行

$ java -XX:CacheDataStore=quarkusapp.aot -jar $YOUR_JAR_FILE

这将生成档案文件,其中包含加速生产运行所需的所有配置文件信息。

现在我们有了这些文件,我们可以使用 Leyden 增强功能来运行我们的应用程序

$ java -XX:CacheDataStore=quarkusapp.aot -XX:+AOTClassLinking -jar $YOUR_JAR_FILE

可能需要的解决方法

由于 Leyden 项目尚处于早期阶段,存在一些已知问题。以下说明对于最终版本来说可能不是必需的,但您今天可能需要它们。

强制使用 G1GC

要从 AOT 归档中的本地编译代码中受益,运行时使用的垃圾收集器需要与您记录 AOT 归档时使用的垃圾收集器相匹配。

请记住,JVM 默认选择的垃圾收集器是基于人体工程学的;这通常很好,但在这种情况下可能会引起一些困惑;例如,如果您在一台大型服务器上构建,它默认会选择 G1GC,但当您在内存受限的服务器上运行应用程序时,它会默认选择 SerialGC。

为避免这种不匹配,最好明确选择垃圾收集器;而且,由于今天许多与 AOT 相关的优化仅适用于 G1,让我们强制使用 G1GC。

强制使用 G1GC

-XX:+UseG1GC

注:您需要在生成 AOT 归档的过程和运行时中始终如一地使用此设置。

强制 G1 区域大小

正如 Quarkus 团队识别并报告给正在处理项目 Leyden 的同事们一样,除了强制使用特定的垃圾收集器之外,还应该确保 AOT 归档中存储的代码是以与运行时将使用的相同的 G1 区域大小生成的,否则可能会导致其错误地识别区域而导致段错误。有关详细信息,请参阅 https://bugs.openjdk.org/browse/JDK-8335440,或者只需设置

显式配置 G1HeapRegionSize

-XX:G1HeapRegionSize=1048576

注:您需要在生成 AOT 归档的过程和运行时中始终如一地使用此设置。

在容器中无法终止

此问题已解决,但如果您使用的是项目 Leyden 的旧版本,并且它在常规容器终止时无法退出,您可能会受到 JDK-8333794 的影响。

JDK-8333794 的解决方法

-Djdk.console=java.basebroken

项目 Leyden 的当前状态

已经有实验性的 Leyden 的早期访问构建版 可以进行测试,这些构建版基于 关于预编译类链接的 JEP 草案

通过 Leyden 项目,将“训练运行”的理念扩展到了新的 AOT 缓存中嵌入的更广泛的数据结构。现在,AOT 进程生成的缓存包含以下数据:

  • 具有历史数据的类文件事件(加载和链接的类,编译)

  • API 点和 indy 的解析(存储在 AOT 归档中的常量池映像中)。如果您的代码中有 lambda,它们将被捕获在这里。

  • Java 堆中预先创建的常量对象(String 和 Class<?> 常量)

  • 执行配置文件和一些编译后的本地代码(所有层级)

Leyden 也是今年 JVM 语言峰会 的热门话题;一旦关于 Leyden 的演讲录像公开可用,我们将在此添加链接。

一些已知限制

这是一个实验性项目,由多个具有不同方法和焦点的团队开发。此处解释的限制在撰写此博文时正在处理中。

主要问题之一是,目前功能仅适用于 x86_64 和 AArch64 架构。

此外,当前开发依赖于扁平的类路径。如果应用程序使用自定义类加载器,那么它可能无法充分受益,因为它可能无法缓存许多类。

如果应用程序大量使用反射,也会发生同样的情况。Quarkus 尽可能避免使用反射,而是倾向于在构建时解析反射调用——因此存在良好的协同作用。

但是,Quarkus 的“fast-jar”模式(这是默认打包模式)将使用自定义类加载器,该类加载器目前会阻碍一些 Leyden 优化。在 Quarkus 中可以使用不同的打包模式来获得 Leyden 更显著的优势,但这样做会禁用其他 Quarkus 优化,因此今天的比较并不完全公平。我们希望在此领域进行改进,以获得所有可能的联合优势。

这些早期发布的重点是引导时间。由于 AOT 加载和链接,引导时间有可衡量的、显著的改进。在某些情况下,这些引导时间的改进会加剧某些应用程序的内存占用。这是一个已知问题,正在处理中,预期的结果也将改进内存占用,因此我们建议在此阶段不要过多担心总内存消耗。

由于 AOT 归档包含特定于机器的优化,例如 C2 编译器生成的本地代码,因此训练运行和生产运行必须在相同类型的硬件和 JDK 版本上进行;它还需要使用相同的基于 JAR 的类路径和相同的命令行选项。

虽然训练运行可以使用与运行应用程序不同的 Main 类,例如模拟使用情况的测试类。

Leyden 的路线图是什么?

对于当前实现中无法在 AOT 中加载和链接的类,仍有工作要做。例如,使用用户定义的类加载器加载的类。训练运行的制作方式也有改进的空间,也许允许用户调整结果以影响决策。

目前,Z 垃圾收集器不支持 AOT 对象归档。正在积极努力确保所有垃圾收集器都与这些增强功能兼容。

Leyden 的路线图还计划了其他事项,例如添加 Condensers。Condensers 将是 AOT 中源代码的可组合转换器,它们会修改源代码以进行优化。每个开发人员都可以定义一个 Condensers 管道,在将源代码编译成字节码之前对其进行优化;这对于 Quarkus 团队来说非常有趣,但 Condensers 尚不可用。

OpenJDK 团队正在积极扩展可以保存到 AOT 缓存并从 AOT 缓存中恢复的编译代码范围。来自 Red Hat OpenJDK 团队的同事们直接参与了这项工作,他们正在研究保存和恢复通常在运行时生成的辅助代码,这些代码用于为“内联”方法提供优化代码,或将编译后的 Java 方法代码链接到实现 JVM、解释器和其他编译后的 C 库的编译后的 C 代码。

Leyden 会取代 GraalVM 的 native-image 功能吗?

简而言之,不会。

如果您想要绝对最小的内存占用,并确保运行时绝对不会发生“动态”调整,那么 GraalVM 原生镜像是最佳选择。想想看:即使以非常精简的形式支持 JVM 通常提供的动态方面,您也需要一些能够执行这项工作的代码,以及一些内存和计算资源来运行这些代码并安全地调整运行时;这是一个复杂的功能,即使 Leyden 的发展远远超出了目前的计划,它也永远不会是完全免费的。

Quarkus 的架构使开发人员能够以严格的“封闭世界”风格定义应用程序,这种方法与 GraalVM 原生镜像结合使用效果极佳,但 Quarkus 的设计在更大、更动态的 JVM 上也确实运行得非常好。

Quarkus 创建封闭世界应用程序的能力并不意味着您必须这样做;事实上,有许多应用程序可以从更多的动态性、更多的运行时可配置性或自适应性中受益,Quarkus 也允许创建这些应用程序,同时仍然受益于与竞争架构甚至竞争运行时和语言相比的非常可观的效率提升。

我们对项目 Leyden 感到非常兴奋,因为它能够显著提高引导时间、预热时间以及整体成本,即使对于“常规”JVM 来说也是如此,从而保留了动态运行时和自适应 JIT 编译器的所有优势,这将是所有那些完全 AOT 原生镜像可能不适合的应用程序的绝佳选择:您将获得原生镜像的一些优势(并非全部),但基本上是免费的,没有任何缺点。

我们还希望它能为“提前”运行(或稍后)某些阶段提供更明确的语义;Mark Reinhold 在此主题上有一篇非常有趣的读物:选择性地转移和约束计算;从 Quarkus 开发者的角度来看,我们可以确认语言规范在这方面的改进将非常受欢迎,并且还将提高使用 GraalVM 原生镜像编译的应用程序的质量和可维护性。

出于这些原因,Quarkus 绝对不会弃用对原生镜像的支持;更有可能的是,最终,“完整 JVM”将始终受益于 Leyden 提供的改进,并且一如既往,我们将努力使这些改进与我们的架构协同工作,并为您带来最少的努力。

基本上,JVM 和原生镜像选项都将从这项倡议中受益。现在是成为一名 Java 开发者的好时机!

我如何确保这对我有用?

确保您的应用程序从 Leyden 中受益的最佳方式是尽早开始尝试并参与开发。从 Quarkus 用户的角度提供真实的反馈将是非常有益的。

如果您花一些时间使用 Leyden 的早期访问构建版 测试您的应用程序,并 报告任何错误 或异常行为,开发人员将考虑您的具体情况。

OpenJDK 问题跟踪器并非对所有人开放,但我们也热烈欢迎您在我们的 Quarkus 频道 上提供反馈;然后我们可以将任何建议转达给直接从事 Leyden 项目的同事。您也可以使用 Leyden 邮件列表