今天和小伙伴们来聊一聊 Spring6 中的一个新特性 AOT(Ahead of Time),这也是目前在学习 Spring6 源码视频的小伙伴的一个提问,其实还是挺有代表意义的,因此松哥整理一篇文章来和大家聊一聊这个话题。
JIT 是即时编译(Just-In-Time Compilation)的缩写。它是一种在程序运行时将代码动态编译成机器码的技术。与传统的静态编译(Ahead-of-Time Compilation)不同,静态编译是在程序执行之前将代码编译成机器码。
JIT 编译器在程序运行时根据需要将代码片段编译成机器码,以提高程序的执行效率。JIT 编译器通常用于解释型语言或动态语言的执行环境中,可以在运行时将解释的代码转换为机器码,从而提高程序的执行速度。
所以 JIT 启动比较慢,因为编译需要占用运行时的资源。
AOT 是预先编译(Ahead-of-Time Compilation)的缩写。它是一种在程序执行之前将代码静态编译成机器码的技术。与即时编译(JIT)不同,即时编译是在程序运行时动态地将代码编译成机器码。AOT 编译器在程序构建或安装阶段将代码转换为机器码,然后在运行时直接执行机器码,而无需再进行编译过程。这种静态编译的方式可以提高程序的启动速度和执行效率,但也会增加构建和安装的时间和复杂性。AOT 编译器通常用于静态语言的编译过程,如 C、C++ 等。
在 Spring 中应用 AOT 需要关注以下注意事项:
Spring Framework 6 引入了AOT(Ahead-Of-Time)编译的概念,这是一种提前编译 Spring 应用程序的技术,以优化运行时性能,减少启动时间,并为创建 GraalVM 原生镜像提供支持。
AOT 的工作原理是在应用程序打包过程中提前执行那些通常在运行时进行的操作。包括生成 Bean 定义、解析配置和处理依赖注入等。通过这种方式,Spring 应用程序可以在启动时跳过这些步骤,从而加快启动速度,并减少 JVM 在运行时的计算负担。
AOT 的使用通常涉及以下几个步骤:
乍一看,AOT 不错呀,还等什么,赶紧用 AOT 来跑我的项目吧!
别急!首先大家看到了 AOT 的有点,但是,这些优点中也隐藏着一些问题:
不过对于这些问题其实也都有办法处理,这就是 AOT 预处理了,这个咱们后文说。
接下来我们就来通过一个案例体验下 AOT 具体应用吧。
Java 虚拟机通常是 JIT 形式,如果我们想要体验 AOT,那么就需要一个既支持 JIT 又支持 AOT 的工具了,这就是 GraalVM。
GraalVM 是一种高性能的通用虚拟机,它为 Java 应用提供 AOT 编译和二进制打包能力,基于 GraalVM 打出的二进制包可以实现快速启动、具有超高性能、无需预热时间、同时需要非常少的资源消耗。
GraalVM 非常有特色的一个功能是提供了 Native Image 打包技术,这种打包方式可以将应用程序打包为一个可脱离 JVM 独立运行的二进制包,这样就省去了 JVM 加载和字节码运行期预热的时间,提升了程序的运行效率。
和我们常用的 HotSpot JVM 相比主要有如下区别:
当然,更重要的是,GraalVM 既支持 JIT 又支持 AOT。
所以,我们需要首先下载并安装 GraalVM。
下载地址:https://www.graalvm.org/downloads/,大家下载和自己 JDK 版本对应的 GraalVM。
这个下载之后直接解压就可以了,解压之后,将 GraalVM 配置到环境变量中就可以了。
最后,还需要安装一下 native-image,当然大家可以顺便用这个安装检验一下自己的 GraalVM 是否配置正确:
接下来我们创建一个 Spring Boot 工程,来体验一下 AOT 提前编译。
首先在创建工程的时候我们多添加一个依赖 GraalVM Native Support
,如下图:
这是一个用来支持 AOT 的插件。
代码创建好之后,我们随便开发一个 /hello
接口,然后就来给项目打包。
直接点击 package 进行打包:
打包结果:
这个就是我们传统的打包方式,没啥好说的。大家注意一下这种传统打包方式打包的时间是 4.86s。
接下来我们来看下 native image 打包。
执行如下命令进行 native image 打包:
mvn clean native:compile -Pnative
打包结果如下图:
大家看这个构建时间超级长。
再来看 native image 构建的结果:
大家看到,除了我们所熟悉的 xxx.jar
,还有一个可执行文件。
因为我这里是 Mac,所以打包出来的可执行文件没有后缀,如果在 Windows 上测试的话,打包出来的就是
aot_demo.exe
了。
现在这两个都可以直接运行。
jar 包就不用说了,大家都比较熟悉了。aot_demo
这个文件则是一个可以脱离 JVM 直接运行的二进制文件,启动效率会高很多。
根据第二小节的介绍,我们知道在打成原生包的时候,Spring AOT 会先进行 AOT 预处理,这个处理过程会创建 Bean 的定义,但是不会实例化 Bean,我们可以分析一下编译的结果就知道了。
首先我的源代码,除了启动类有两个类,分别是:
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello() {
return helloService.sayHello();
}
}
@Service
public class HelloService {
public String sayHello() {
return "hello aot";
}
}
在打 native-image 的时候,我们看下结果:
看过松哥之前将的 Spring 源码分析的小伙伴,这块的代码应该都很好明白,这就是直接把 BeanDefinition 给解析出来了,不仅注册了当前 Bean,也把当前 Bean 所需要的依赖给注入了,将来 Spring 执行的时候就不用再去解析 BeanDefinition 了。
同时我们可以看到在 META-INF 中生成了 reflect、resource 等配置文件。这些是我们添加的 native-maven-plugin 插件所分析出来的反射以及资源信息,将自动将这些作为配置文件生成的。
这块其实能聊的还蛮多,而且作为一个新支持的特性,Spring 对其功能也在不断完善,松哥后面会继续跟大家捋一捋这块的内容.