JEP 544: 提前代码编译

Hacker News Top 新闻

摘要

JEP 544 介绍了用于 Java 应用程序的提前代码编译,旨在通过在训练运行中将代码编译为原生代码来改善启动和预热时间,并利用动态 JIT 编译以适应不断变化的工作负载。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/09/10 20:16

# JEP 544:提前编译代码 来源:https://openjdk.org/jeps/544 ## 概要 通过为应用程序生成优化的原生代码,并在HotSpot Java虚拟机启动时使其立即可用,从而改善启动时间和预热时间。这通过在训练运行中将应用程序代码编译为原生代码,并将其存储在AOT缓存(https://openjdk.org/jeps/483#Description)中以供后续生产环境运行使用来实现。如果生产环境中的工作负载发生变化,则动态重新生成原生代码以保持峰值性能,从而兼具提前编译(AOT)和即时编译(JIT)的优势。 ## 目标 - 使应用程序能够更快地达到峰值性能。 - 使应用程序即使在工作负载发生变化时也能持续保持峰值性能。 - 无需对应用程序、库或框架的代码进行任何更改。 - 无需对HotSpot的配置进行任何更改,只需请求使用AOT缓存。 - 继续支持Serial、Parallel、G1和ZGC垃圾收集器(https://openjdk.org/jeps/516)。 - 不引入新的AOT工作流,而是扩展现有的AOT缓存创建工作流(https://openjdk.org/jeps/514)。 - 确保从AOT编译代码到JIT编译代码的切换对应用程序透明。 - 支持AArch64和x64处理器架构。 ## 非目标 - 目标不是提供仅AOT模式。应用程序将在同一运行中同时使用AOT编译代码和JIT编译代码,并根据需要自动切换。 - 目标不是支持交叉编译。在训练运行中编译的代码必须在相同的CPU架构上运行,并具有相同的功能集,用于后续的生产运行。 - 目标不是支持HotSpot当前支持的所有CPU架构。我们期望常规的移植活动最终能支持所有主要架构。 ## 动机 当HotSpot JVM运行Java应用程序时,它会经历三个阶段:启动、预热,然后达到峰值性能。 在*启动*期间,HotSpot调用应用程序的`main`方法,并按需加载、链接和初始化类。最初,它通过字节码解释器运行应用程序和JDK库代码,这很慢。在解释器内部,HotSpot通过统计方法调用和循环迭代等事件来*分析*应用程序的行为。它使用分析数据来选择频繁调用的方法,即热点(https://en.wikipedia.org/wiki/Hot_spot_(computer_programming)),并通过基础的C1编译器将它们编译为原生代码。这种原生代码仅经过适度优化。 在*预热*期间,应用程序进入其工作负载状态,类的加载、链接和初始化逐渐减少。HotSpot继续分析应用程序,既在字节码解释器中,也通过C1插入的检测代码进行。它收集更丰富的分析信息,不仅包括方法调用和循环迭代计数,还包括遇到的对象类型。随着时间的推移,分析数据不断积累,其统计有效性也更高。最终,HotSpot使用这些数据来选择最热的方法,并通过高级的C2编译器将它们编译为原生代码。这种原生代码不包含检测代码,并且经过高度优化。 分析应用程序和生成原生代码并非没有代价。字节码解释器不仅运行缓慢,而且经过检测的原生代码比未经检测的原生代码更慢。将方法编译为原生代码需要CPU时间和内存,这些资源本可用于应用程序,即使HotSpot仅在分析数据表明这样做值得时才编译方法。然而,随着JIT编译逐渐跟上应用程序新出现的热点,应用程序的运行速度会加快。最终,所有热点方法都被编译为完全优化的原生代码,编译器进入空闲状态。 只要应用程序的热点不发生变化,它就会保持在这种*峰值性能*状态。然而,应用程序的热点可能会因工作负载的变化而发生变化。当这种情况发生时,HotSpot可以动态*去优化*(根据需要丢弃先前生成的原生代码)和*重新优化*(为新出现的热点方法生成新的原生代码)。例如,如果应用程序最初接收两种类型的请求,那么HotSpot会针对这两种请求类型动态优化代码。如果应用程序开始接收第三种类型的请求,HotSpot可以动态去优化,然后为所有三种类型的请求重新优化代码。实际上,应用程序可以经历另一个预热阶段,在工作负载变化时保持性能。 ### 那么静态编译呢? 静态编译有时被提议作为Java代码动态编译的替代方案。静态编译器在运行时之前将整个应用程序提前转换为原生代码。 静态编译相对于动态编译有一些优势。静态编译的应用程序可以立即启动并达到峰值性能,无需预热阶段。运行时无需字节码解释器、分析或编译。如果静态编译器的优化工作由先前运行收集的准确分析数据指导,其峰值性能甚至可以与HotSpot相媲美。 然而,动态编译相对于静态编译有三个关键优势。 首先,动态编译使应用程序具有*敏捷性*,因为它能响应应用程序热点的变化。它根据需要进行去优化和重新优化,在应用程序工作负载变化时保持性能。静态编译的应用程序无法以这种方式响应——本质上,它只能为一组热点进行优化。 其次,动态编译使应用程序在各种硬件和软件上具有*可移植性*,因为它在运行时生成特定于运行时环境的原生代码。如果应用程序重新部署在不同的处理器架构、具有不同功能集的处理器、不同的操作系统或不同版本的JDK上,HotSpot将无需对应用程序进行任何更改即可在该环境中达到峰值性能。面对这种变化,静态编译的应用程序必须重新编译。 最后,动态编译与Java平台的动态特性*兼容*。动态类加载、动态链接、动态分派和动态反射等特性带来了强大的表达能力,并且一直是该平台成功的基础。HotSpot自然地处理这些特性,而静态编译器则力不从心。即使进行大量的静态分析,也无法弥补这些特性需要在运行时做出许多决策的事实。因此,Java代码的静态编译器实现者不得不采用不兼容的约束,例如封闭世界假设,或者给开发者带来重大负担,例如必须提前标识适合反射的类。 ### 将编译工作转移到训练运行 在整个启动和预热阶段,HotSpot持续处理多项任务:它运行应用程序和JDK库代码;按需加载、链接和初始化类;分析应用程序执行情况;并在分析数据的指导下,以不同程度的优化将热点方法编译为原生代码。 Project Leyden(https://openjdk.org/projects/leyden/)的理念是,改善启动和预热时间的关键是将部分工作提前完成,而不是即时完成。我们通过在*训练运行*中完成工作来提前完成部分工作,并将工作结果存储在AOT缓存(https://openjdk.org/jeps/483#Description)中,以便在后续*生产运行*中即时使用。 我们通过JEP 483(https://openjdk.org/jeps/483)提前完成了类加载和链接工作,该JEP随JDK 24交付。AOT缓存存储训练运行中已加载和链接的类形式,从而改善启动时间。 我们通过JEP 515(https://openjdk.org/jeps/515)提前完成了分析工作,该JEP随JDK 25交付。AOT缓存存储训练运行中调用的方法的执行分析数据,使C2编译器能在生产运行开始时立即运行,从而改善预热时间。 这些改进为我们最终目标奠定了基础,即将编译和优化工作提前完成。AOT缓存将存储在训练运行中编译的优化原生代码,使HotSpot能够即时加载该代码,而无需在每次生产运行开始时重新编译。这将同时改善启动时间和预热时间。 HotSpot并不总是使用缓存代码;如果应用程序的工作负载发生变化,那么HotSpot可以像往常一样进行去优化和重新优化,为新出现的热点方法生成新的原生代码以保持性能。因此,Java应用程序将获得静态编译的部分优势,同时保留动态编译的敏捷性、可移植性和兼容性。 ## 描述 我们扩展现有的AOT缓存以存储在训练运行中生成的优化原生代码。这种缓存代码称为*AOT代码*。在生产运行期间,如果缓存中找到匹配的AOT代码,则可以即时满足方法的优化代码请求。如果AOT代码不可用、不兼容、不适用或稍后被去优化,则执行回退到现有的解释器和JIT机制。AOT代码和JIT代码可以共存并完全互操作,因为它们是由相同的编译器(C1和C2)创建的。 要创建AOT缓存,请使用`AOTCacheOutput`选项运行您的应用程序训练运行并生成AOT代码: ``` $ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App ... ``` 此工作流与以前的版本相同。然而,文件`app.aot`中的AOT缓存现在不仅包含预链接类和分析数据,还包含所选热点方法的AOT代码。随后,在生产环境中,您可以使用缓存运行应用程序: ``` $ java -XX:AOTCache=app.aot -cp app.jar com.example.App ... ``` 无需额外的选项或设置来生成或使用AOT代码。HotSpot默认创建AOT代码并将其存储在缓存中。它还继续将分析数据存储在缓存中,用于协调AOT代码的加载以及指导后续JIT代码的生成。 ### 性能 为了评估AOT代码的启动优势,我们运行了五个使用流行Java框架构建的基准应用程序。我们在一个双核Linux/x64系统上运行它们,以模拟微服务环境,其中JIT编译器可能与应用程序竞争CPU时间,从而增加启动时间: 性能图表显示了五个基准测试的AOT代码启动优势 没有AOT代码时,AOT缓存将这些应用程序的启动时间减少了约50%至70%;有了AOT代码,缓存将其启动时间减少了约65%至80%。 为了评估AOT代码的预热优势,我们运行了一个`javac`基准应用程序,该程序重复编译相同的50个源文件二十次,测量每次迭代所需的时间: 性能图表显示了AOT代码对javac的预热优势 在每条曲线中,第一次迭代显示了启动时间的改进:没有AOT代码的AOT缓存将启动时间提高了约30%;添加AOT代码带来了额外的45%的改进,总计约75%。后续迭代显示了预热阶段,期间HotSpot编译最热的方法:随着原生代码质量的提高和编译器完成工作,迭代时间趋于下降并最终达到稳定状态。没有AOT代码的AOT缓存曲线比没有AOT缓存的曲线下降得更快,最终达到大致相同的稳定状态。带有AOT代码的缓存曲线在第四次迭代时已经接近稳定状态。顶部曲线和底部曲线之间的区域代表总预热时间改进。所有这些基准测试的信息,包括运行说明和源代码链接,均可在此处找到(https://github.com/openjdk/leyden/tree/premain#5-benchmarking)。 ### AOT代码与JIT代码的区别 AOT代码和JIT代码可能不同,因为训练运行和生产运行可能不同。 一个差异来源是类初始化的顺序在训练和生产运行之间可能不同,尤其是在工作负载不同时。访问另一个类中的静态字段或调用静态方法的方法必须确保该类已初始化。因此,在使用C2生成AOT代码时,HotSpot会编译此类方法的两个版本:慢速版本包含额外的代码以确保引用类的初始化,而快速版本不包含该代码,因此可以更好地优化。HotSpot最初使用慢速版本,然后在所有引用类初始化后切换到快速版本。 另一个差异来源是静态最终字段的值可能因运行而异;例如,它可能使用当前日期和时间初始化。在即时编译引用此类字段的方法时,包含该字段的类将已初始化,因此该字段的值是已知的,C2可以将其视为编译时常量,直接嵌入原生代码中。然而,在提前编译相同的方法时,没有类会被初始化,因此该字段的值未知,C2不能将其视为编译时常量;它必须生成显式加载该字段的代码。 尽管存在这些差异,AOT代码仍然以对应用程序透明的方式提供显著的性能优势。与往常一样,在运行时,编译器可以生成JIT代码来替换那些未能保持良好效果的AOT代码。 ### 训练运行和生产运行的一致性 要享受在训练运行中生成的AOT缓存的好处,训练运行和所有后续生产运行必须基本相似,如JEP 483(https://openjdk.org/jeps/483#Consistency-of-training-and-subsequent-runs)中所述。 如果AOT缓存包含AOT代码,则在满足以下两个附加约束时使用该代码: - 所有运行都使用相同架构和相同功能的CPU。例如,为具有AVX-512(https://en.wikipedia.org/wiki/Advanced_Vector_Extensions)向量指令功能的x64 CPU生成的AOT代码将无法在没有该功能的x64 CPU上运行。 - 所有运行都使用相同的垃圾收集器,因为AOT代码包含特定于GC的读写屏障。 如果这些约束未满足,则HotSpot会发出警告消息并且不加载AOT代码,回退到通常的解释器和JIT机制。它仍然使用AOT缓存中的其他信息,即已加载和链接的类以及分析数据。在这种情况下,应用程序的启动和预热可能会变慢,但其执行仍然正确,并且最终仍将达到峰值性能。 ### 在生产环境中观察AOT缓存使用情况 您可以通过现有的HotSpot选项`PrintCompilation`(https://docs.oracle.com/en/java/javase/26/docs/specs/man/java.html#advanced-jit-compiler-options-for-java)观察生产运行中是否加载了AOT代码,该选项现在同时报告AOT代码加载和JIT编译: ``` $ java -XX:+PrintCompilation \ -XX:AOTCache=app.aot -cp app.jar com.example.App ... ``` 您还可以通过`-XX:+UnlockDiagnosticVMOptions -XX:+PrintAOTCache`选项检查AOT缓存的内容。

相似文章

JEP 539:JVM中的严格字段初始化已移至预览

Hacker News Top

JEP 539在JVM中引入了严格初始化的字段作为预览功能,要求字段在读取前必须初始化,以避免出现0或null等默认值。这为基于JVM的语言提供了更强的完整性保证。

JIT编译代码在5μs内完成

Hacker News Top

文章探讨了AI辅助如何简化了创建具有亚微秒级编译时间的快速JIT编译器的过程,并通过一个基于Rust的正则表达式引擎示例进行了演示。

1jehuang/jcode

GitHub Trending (daily)

jcode 是一个开源编码代理工具,专为多会话工作流设计,资源占用低,提供CLI安装方式,并在性能上优于Claude Code和Cursor Agent等现有代理。

rustc_codegen_jvm: 可生成JVM字节码的Rust编译器后端

Lobsters Hottest

rustc_codegen_jvm 是一个自定义的Rust编译器后端,能够生成JVM字节码,从而将Rust代码编译成可在JVM 8+上运行的JAR文件。它支持多种Rust特性,包括控制流、数据结构、特征(traits)和闭包。