1. 概述
Spring 6 引入了一个旨在优化应用性能的新特性:预先编译(Ahead-of-Time, AOT)支持。本文将探讨Spring 6的AOT优化功能如何工作、其优势以及如何使用它。
2. 预先编译
2.1. 即时编译器(JIT)
对于大多数使用的Java虚拟机(JVM),如Oracle的HotSpot JVM和OpenJDK,当编译源代码(.java文件)时,产生的字节码会被存储在.class文件中。这样,JVM会使用即时编译器将字节码转换为机器代码。
此外,即时编译涉及JVM在运行时对字节码的解释,并将经常执行的代码动态编译为原生机器代码。
2.2. 预先编译器(AOT)
预先编译(AOT)是一种在应用程序运行前将字节码编译为原生机器代码的技术。
Java虚拟机通常不支持此功能。然而,Oracle在OpenJDK项目中的HotSpot JVM实验性地发布了名为“GraalVM Native Image”的AOT功能,允许预先编译代码。
预编译后,计算机处理器可以直接执行代码,无需JVM解释字节码,从而提高了启动时间。
在这篇文章中,我们将不会详细讨论AOT编译器。有关AOT编译的详细介绍,请参考我们的另一篇文章:预先编译(AOT)概述
3. Spring 6中的AOT
3.1. AOT优化
构建Spring 6应用时,我们需要考虑三种不同的运行时选项:
- 在JRE上运行的传统Spring应用。
- 在编译阶段AOT阶段生成的代码,在JRE上运行。
- 在编译阶段AOT阶段生成的代码,在GraalVM原生映像中运行。
让我们来看看Spring 6的新功能——第二种选择(第一种是传统的构建,另一种是原生映像)。
首先,我们需要设置环境以支持AOT编译:获取GraalVM的最新入门指南。
通过AOT编译构建应用在性能和资源消耗方面具有多个优势:
- 死代码消除:AOT编译器可以移除在运行时从未执行过的代码,这可以通过减少需要执行的代码量来提高性能。
- 内联:内联是一种技术,AOT编译器将函数调用替换为实际的函数代码。这可以减少函数调用的开销,从而提高性能。
- 常量传播:AOT编译器通过在编译时替换可确定的常量值来优化性能,消除了运行时计算的需求,从而提高性能。
- 跨过程优化:AOT编译器可以通过分析程序的调用图来优化跨多个函数的代码,减少函数调用的开销并识别常见的子表达式,从而提高性能。
- 对象定义: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优化中的问题
当我们决定使用AOT编译构建应用时,可能会遇到一些问题,例如:
- 反射:它允许在编译时无法确定的动态调用方法和访问字段。AOT编译器无法确定动态调用的类和方法。
- 配置文件:配置文件的内容可能在运行时更改。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. 总结
本文介绍了Spring 6的AOT优化新特性。
如往常一样,示例的完整源代码可以在GitHub上找到。