1、概览 {#1概览}
Spring 6 提供了一项新功能,有望优化应用程序的性能: Ahead-of-Time(AOT) 编译支持。
在本文中,我们将了解 Spring 6 中 AOT 优化功能的工作原理、优点以及使用方法。
2、AOT 编译 {#2aot-编译}
2.1、JIT 编译器 {#21jit-编译器}
对于最常用的 Java 虚拟机(JVM),如 Oracle 的 HotSpot JVM 和 OpenJDK,当我们编译源代码(.java
文件)时,生成的字节码存储在 .class
文件中。这样,JVM 就会使用 JIT(Just-In-Time,即时编译) 编译器将字节码转换为机器码。
此外,JIT 编译涉及 JVM 对字节码的解释,以及在运行时将频繁执行的代码动态编译为本地机器码。
2.2、AOT 编译器 {#22aot-编译器}
Ahead-of-Time(AOT,提前编译或预编译)是一种在应用程序运行前将字节码预编译为本地机器码的技术。
Java 虚拟机(JVM)通常不支持这一功能。不过,甲骨文已在 OpenJDK 项目中发布了一项针对 HotSpot JVM 的实验性 AOT 功能,名为 "GraalVM Native Image",允许进行 AOT 编译。
预编译代码后,计算机处理器可直接执行代码,无需 JVM 解释字节码,从而缩短了启动时间。
关于 AOT 编译器的更多细节可以参考 官方文档。
3、Spring 6 中的 AOT 支持 {#3spring-6-中的-aot-支持}
3.1、AOT 编译优化 {#31aot-编译优化}
在构建 Spring 6 应用程序时,我们需要考虑三种不同的运行时选项:
- 运行在 JRE 上的传统 Spring 应用程序。
- 在 AOT 编译阶段生成并在 JRE 上运行的代码。
- 在 AOT 编译阶段生成并在 GraalVM 原生镜像中运行的代码。
让我们来看看第二种方案,它是 Spring 6 的全新功能(前者是传统构建,后者是原生镜像)。
首先,我们需要为 AOT 编译 设置好环境。
通过 AOT 编译构建的应用程序在性能和资源消耗方面具有多重优势:
- 消除死代码:AOT 编译器可以消除运行时从未执行过的代码。这样可以减少需要执行的代码量,从而提高性能。
- 内联:内联是 AOT 编译器用函数的实际代码,替换函数调用的一种技术。这可以减少函数调用的开销,从而提高性能。
- 常量传播:AOT 编译器通过在编译时确定变量的常量值来替换变量,从而优化性能。这样就无需进行运行时计算,从而提高了性能。
- 过程间优化:AOT 编译器可通过分析程序的调用图来优化跨多个函数的代码。这可以通过减少函数调用的开销和识别常见的子表达式来提高性能。
- Bean 定义:Spring 6 中的 AOT 编译器可减少不必要的
BeanDefinition
实例,从而提高应用程序的效率。
因此,让我们使用 AOT 优化命令来构建应用程序:
mvn clean compile spring-boot:process-aot package
然后使用命令运行应用程序:
java -Dspring.aot.enabled=true -jar <jar-name>
我们可以设置构建插件,默认启用 AOT 编译:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
3.2、AOT 优化中的问题 {#32aot-优化中的问题}
当我们决定使用 AOT 编译来构建应用程序时,可能会遇到一些问题,例如:
- 反射:它允许代码动态调用编译时未知的方法和访问未知的字段。AOT 编译器无法确定动态调用的类和方法。
- Properties 文件:properties 文件的内容可能在运行时发生变化。AOT 编译器无法确定动态使用的属性文件。
- 代理:代理通过提供另一个对象的代理来控制对该对象的访问。由于代理可用于将方法调用动态重定向到其他对象,因此 AOT 编译器很难确定运行时将调用哪些类和方法。
- 序列化:序列化将对象的状态转换为字节流,反之亦然。总的来说,这会使 AOT 编译器难以确定哪些类和方法将在运行时被调用。
为了确定哪些类会导致 Spring 应用程序出现问题,我们可以运行一个代理来提供有关反射操作的信息。
因此,让我们配置 Maven 插件,使其包含一个 JVM 参数,以协助实现这一点。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments>
-agentlib:native-image-agent=config-output-dir=target/native-image
</jvmArguments>
</configuration>
<!- ... -->
</plugin>
用如下命令运行:
./mvnw -DskipTests clean package spring-boot:run
在 target/native-image/
中,我们可以找到生成的文件,如 reflect-config.json
、resource-config.json
等。
如果在该文件中定义了某些内容,就需要定义 RuntimeHints
,以便正确编译可执行文件。
4、总结 {#4总结}
在本文中,我们介绍了 Spring 6 中新的 AOT 优化功能。
参考:https://www.baeldung.com/spring-6-ahead-of-time-optimizations