Java在云原生的破局利器——AOT(JIT与AOT)

导读

JIT(Just-in-Time,实时编译)一直是Java语言的灵魂特性之一,与之相对的AOT(Ahead-of-Time,预编译)方式,似乎长久以来和Java语言都没有什么太大的关系。但是近年来随着Serverless、云原生等概念和技术的火爆,Java JVM和JIT的性能问题越来越多地被诟病,在Golang、Rust、NodeJS等新一代语言的包夹下,业界也不断出现“云原生时代,Java已死”的言论。那么,Java是否可以使用AOT方式进行编译,摆脱性能的桎梏,又是否能够在云原生时代焕发新的荣光?本文会带着这样的疑问,去探索Java AOT技术的历史和现状。

上上篇有讲过,HotSpot JVM中集成了两种JIT编译器,Client Compiler和Server Compiler,它们的作用也不同。Client Compiler注重启动速度和局部的优化,Server Compiler则更加关注全局的优化,性能会更好,但由于会进行更多的全局分析,所以启动速度会变慢。两种编译器有着不同的应用场景,在虚拟机中同时发挥作用。而随着时间的发展,不论是Client Compiler还是Server Compiler都发展出了各具特色的实现,如 C1、C2、Graal Compiler等,你可以在JVM启动参数中选择自己所需的JIT编译器实现。

从JDK 10起,HotSpot虚拟机同时拥有三种不同的即时编译器。此前我们已经介绍了经典的客 户端编译器和服务编译,还有全新的即时编译器:Graal编译器。

JIT与AOT的区别

提前编译是相对于即时编译的概念,提前编译能带来的最大好处是Java虚拟机加载这些已经预编译成二进制库之后就能够直接调用,而无须再等待即时编译器在运行时将其编译成二进制机器码。理论上,提前编译可以减少即时编译带来的预热时间,减少Java应用长期给人带来的“第一次运行慢"的不良体验,可以放心地进行很多全程序的分析行为,可以使用时间压力更大的优化措施。但是提前编译的坏处也很明显,它破坏了Java"—次编写,到处运行"的承诺,必须为每个不同的硬件、操作系统去编译对应的发行包;也显著降低了Java链接过程的动态性,必须要求加载的代码在编译期就是全部已知的,而不能在运行期才确定,否则就只能舍弃掉己经提前编译好的版本,退回到原来的即时编译执行状态。

AOT的优点

  • 在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗
  • 可以在程序运行初期就达到最高性能,程序启动速度快
  • 运行产物只有机器码,打包体积小

AOT的缺点

  • 由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT
  • 没有动态能力
  • 同一份产物不能跨平台运行

Java AOT的历史演进

JIT是Java的一大灵魂特性,得益于即时编译,Java语言同时拥有了不输编译型语言的运行速度和“一次编译、到处运行”的跨平台能力、甚至还拥有和解释型语言类似的动态性能力,可以说JIT是Java语言能够快速风靡全球并得到广泛应用的重要原因之一。因此在Java诞生至今的几十年里,AOT编译方式和Java可以说是“一毛钱关系都没有”,那么为什么今天我们又要提起以AOT的方式运行Java程序呢,是JIT它不香么?

其实Java本身一直存在着一些“问题”:JVM本身是很重的,因此对服务器的性能消耗(某种意义上可以说是性能浪费)是很高的,同时Java应用的启动速度也往往被人所诟病。但是这些问题在Java所带来的跨平台运行能力和动态特性面前,都是“值得的牺牲” —— 使用Java你可以更方便的进行代码的打包和交付,可以轻松写出性能不差的程序并部署在任何主流的OS上。这些对于企业用户而言,一直是技术选型非常重要的考量因素,直到Docker和Serverless的诞生,逐渐改写了这一切:

Docker的诞生,让底层运行环境变得可以随意定制,你可以在生产环境的任何一台服务器上轻松混部Windows和Linux的各种发行版,这让JVM的跨平台能力显得不那么重要了。

Serverless概念的火爆,让弹性伸缩能力成为服务端程序的一大重要目标,这时候JVM的“臃肿”和JIT导致的启动延迟就让Java程序显得很不“Serverless”(拉包时间长、启动速度慢),如今我们提到云原生总是先想到Go、NodeJS而不是Java,似乎Java和云原生已经不是一个时代的产物了。

所以如果想让Java在云原生时代焕发“第二春”,支持AOT是非常重要的一步,而在这一步上,Java语言却经历了一波三折:

2016年,OpenJDK的 JEP 295提案首次在Java中引入了AOT支持,在这一草案中,JDK团队提供了 jaotc 工具,使用此工具可以将指定class文件中的方法逐个编译到native代码片段,通过Java虚拟机在加载某个类后替换方法的的入口到AOT代码来实现启动加速的效果。

jaotc的类似于给JVM打了一个“补丁”,让用户有权利将部分代码编译成机器码的时期提前,并预装载到JVM中,供运行时代码调用。不过这个补丁存在很多问题:

首先是在设计上没有考虑到Java的多Classloader场景,当多个Classloader加载的同名类都使用了AOT后,他们的static field是共享的,而根据java语言的设计,这部分数据应该是隔开的。由于这个问题无法快速修复,jaotc最终给出的方案只是暴力地禁止用户自定义classloader使用AOT。

此外,由于社区人手不足,缺乏调优和维护,jaotc的实际运行效果不尽人意,有时甚至会对应用的启动和运行速度带来反向优化,实装没多久之后就退化为实验特性,最终在JDK 16中被删除,结束了短暂的一生。

后来阿里AJDK团队自研的AppCDS(Class-Data-Share)技术继承了jatoc的思路,进行了大幅的优化和完善,目前也不失为一种Java AoT的选择,其本质思路和jaotc基本一致 ,这里就不再赘述了。

而目前业界除了这种在JVM中进行AOT的方案,还有另外一种实现Java AOT的思路,那就是直接摒弃JVM,和C/C++一样通过编译器直接将代码编译成机器代码,然后运行。这无疑是一种直接颠覆Java语言设计的思路,不过还是被各路大佬们实现了,那就是GraalVM Native Image。它通过C语言实现了一个超微缩的运行时组件 —— Substrate VM,基本实现了JVM的各种特性,但足够轻量、可以被轻松内嵌,这就让Java语言和工程摆脱JVM的限制,能够真正意义上实现和C/C++一样的AOT编译。这一方案在经过长时间的优化和积累后,已经拥有非常不错的效果,基本上成为Oracle官方首推的Java AOT解决方案,接下来我们会重点分析一下这项技术的原理和实际应用。

新的破局点GraalVM

先说一下GraalVM,这是Oracle在2019年推出的新一代UVM(通用虚拟机),它在HotSpotVM的基础上进行了大量的优化和改进,主要提供了两大特性:

  • Polyglot:多语言支持,你可以在GraalVM中无缝运行多种语言,包括Java、JS、Ruby、Python甚至是Rust。更重要的是可以通过GraalVM的API来实现语言混编 —— 比如在一段Java代码中无缝引用并调用一个Python实现的模块。
  • HighPerformance:高性能,首先它提供了一个高性能的JIT引擎,让Java语言在GraalVM上执行的时候效率更高速度更快 ;其次就是提供了SubstrateVM,通过Graal Compiler你可以将各种支持的语言(包括Java)编译成本地机器代码,获得更好的性能表现。

值得一提的是,Substrate VM虽然名为VM,但并不是一个虚拟机,而是一个包含了 垃圾回收、线程管理 等功能的运行时组件(Runtime Library),就好比C++当中的stdlib一样。当Java程序被编译为Native Image运行的时候,并不是运行在Substrate VM里,而是将SubstrateVM当作库来使用其提供的各种基础能力,以保障程序的正常运行。

不难看出,GraalVM这个项目的野心是非常大的,可以说这个项目是Oracle抢占云原生市场的一个重要布局,随着官方的不断投入和社区的壮大,目前GraalVM已经日渐成熟,在高性能和跨语言支持方面都交出了令人满意的答卷。GraalVM本身是一个非常庞大的项目,有很多的细节点可以深挖,不过接下来我们还是重点研究一下它的AOT能力 —— Native Image。

Native Image:原理与限制

一个Java程序究竟是如何被编译成静态可执行文件的?我们先来看一下NativeImage的原理。

Native Image的输入是整个应用的所有组件,包括应用本身的代码、各种依赖的库、JDK库、以及SVM;首先会进行整个应用的初始化,也就是代码的静态分析,这个分析过程有点类似GC中的“可达性分析”,会讲程序运行过程中将所有可达的代码、变量、对象生成一个快照,最终打包成一个可执行的Native Image。

一个完整的Native Image包含两个部分,一部分称为 Text Section,即用户代码编译成的机器代码;另一部分称为 Data Section,存储了应用启动后堆区内存中各种对象的快照。

可以预见的是,这个静态分析的过程(官方称之为 Pionts-to Analysis)是非常复杂且耗时的,整个分析过程会以递归的方式进行,最终得到两个树形结构Call Tree(包含所有可达的方法)以及Object Tree(包含所有可达的对象),Call Tree中所包含的方法会被AOT编译为机器码,成为Native Image的Text Section,而Object Tree中所包含的对象及变量则会被保存下来,写入Native Image的Data Setion。

整个静态分析的算法非常复杂,目前网上相关的资料也较少,如果有对具体算法感兴趣的同学,官方团队在Youtube上有一个相对比较详细的算法说明视频,可以自行查看:https://www.youtube.com/watch?v=rLP-8q3Cb8M

作为一个Java程序员,你一定会好奇JVM的动态特性,例如反射、代理,要如何进行静态分析呢?很显然,这两者之间是存在冲突的,因此Native Image设置了一个名为“Closed World”的假设作为静态分析的基本前提。

这个基本前提包含三个要求,对应的也就是目前Native Image存在的三个限制:

  1. Points-to分析的时候,需要接受完整的字节码作为输入(即项目中所有用到的class的字节码都需要获取的到)。

=> 在运行期动态生成或者是动态获取字节码的程序,无法构建成 Native Image。

  1. Java的动态特性,包括反射、JNI、代理,都需要通过配置文件在构建前实现声明好。

=> 无法提前声明动态特性使用范围的程序,无法构建成Native Image (例如,根据用户输入的一个参数反射去调用某个方法)。

  1. 在整个运行过程中,程序不会再加载任何新的class。

=> 在运行期间执行动态编译,或者是自定义Classloader动态装载类的程序,无法构建成Native Image。

Native Image:环境安装

Graal的安装

Native Image:实践

介绍了Native Image的基本原理和限制后,让我们来实际实践看看这项技术到底能够带给我们什么。

这里我们先给出一个非常基础的DEMO代码:

代码语言:javascript
复制
public class HelloWorld {
private static final String CONST = "this-is-a constant var";

private String name;

public HelloWorld(String name) {
    this.name = name;
}

public void sayHello() {
    System.out.println("hello, " + name);
}

public static void main(String[] args) {
    System.out.println(CONST);
    HelloWorld h1 = new HelloWorld("lumin");
    HelloWorld h2 = new HelloWorld(args[0]);
    h1.sayHello();
    h2.sayHello();
}

}

如何将这段代码构建成Native Image呢?首先安装好GraalVM,然后先使用javac将代码编译成字节码:

代码语言:javascript
复制
$ javac HelloWorld.java

接下来执行Native Image Build,指定类名

代码语言:javascript
复制
$ native-image HelloWorld

整个构建过程会执行比较长的一段时间,主要是执行Points-Analysis过程较长(大约三分多钟),最终的产物就是一个二进制文件:

可以看到这个HelloWorld最终打包产出的二进制文件大小为8.2M,这是包含了SVM和JDK各种库后的大小,虽然相比C/C++的二进制文件来说体积偏大,但是对比完整JVM来说,可以说是已经是非常小了。

再对比下运行速度:

可以看到,相比于使用JVM运行,Native Image的速度要快上不少,cpu占用也更低一些,从官方提供的各类实验数据也可以看出Native Image对于启动速度和内存占用带来的提升是非常显著的:

接下来我们加上 -H:+PrintImageObjectTree -H:+ExhaustiveHeapScan -H:+PrintAnalysisCallTree的参数再进行一次build,这样可以将整个Points-to Analysis的详细过程(Object Tree和Call Tree)打印出来以供分析:

call_tree_xxx文件中会包含完整的方法调用树,可以看到是一个递归的树形结构

通过Call Tree就可以得到整个程序运行过程中所有可能用到的方法,这些方法的代码都会被编译为机器码。

object_tree_xxx文件中,则包含了代码中所有使用到的对象和变量:

这里存储的主要是各种静态对象和变量,它们最终都被被打包至Image Heap中。

最后我们再来看一个使用反射的例子:

代码语言:javascript
复制
public class HelloReflection {

public static void foo() {
    System.out.println("Running foo");
}

public static void bar() {
    System.out.println("Running bar");
}

public static void main(String[] args) {
    for (String arg : args) {
        try {
            HelloReflection.class.getMethod(arg).invoke(null);
        }
        catch (ReflectiveOperationException ex) {
            System.out.println("Exception running" + arg + ": "+ ex.getClass ().getSimpleName());
        }
    }
}

}

这段代码接收用户的输入作为入参,然后通过反射调用用户指定的方法,我们通过普通方式来编译执行这段代码,是可以正常Work的:

代码语言:javascript
复制
$ java HelloReflection foo bar
Running foo
Running bar

代码语言:javascript
复制
d:\test>native-image HelloReflection

GraalVM Native Image: Generating 'helloreflection' (executable)...

[1/7] Initializing... (5.9s @ 0.08GB)
Version info: 'GraalVM 22.2.0 Java 11 CE'
Java version info: '11.0.16+8-jvmci-22.2-b06'
C compiler: cl.exe (microsoft, x64, 19.32.31332)
Garbage collector: Serial GC
[2/7] Performing analysis... [*****] (6.9s @ 1.05GB)
2,695 (73.98%) of 3,643 classes reachable
3,437 (53.28%) of 6,451 fields reachable
12,173 (45.34%) of 26,851 methods reachable
26 classes, 0 fields, and 267 methods registered for reflection
62 classes, 53 fields, and 52 methods registered for JNI access
1 native library: version
[3/7] Building universe... (1.0s @ 0.58GB)

Warning: Reflection method java.lang.Class.getMethod invoked at HelloReflection.main(HelloReflection.java:14)
Warning: Aborting stand-alone image build due to reflection use without configuration.
Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception

                    0.7s (5.0% of total time) in 14 GCs | Peak RSS: 1.94GB | CPU load: 6.07

========================================================================================================================
Failed generating 'helloreflection' after 14.1s.

GraalVM Native Image: Generating 'helloreflection' (executable)...

[1/7] Initializing... (5.7s @ 0.08GB)
Version info: 'GraalVM 22.2.0 Java 11 CE'
Java version info: '11.0.16+8-jvmci-22.2-b06'
C compiler: cl.exe (microsoft, x64, 19.32.31332)
Garbage collector: Serial GC
[2/7] Performing analysis... [] (7.5s @ 0.32GB)
2,807 (74.79%) of 3,753 classes reachable
3,564 (53.47%) of 6,666 fields reachable
12,667 (45.85%) of 27,625 methods reachable
26 classes, 0 fields, and 272 methods registered for reflection
62 classes, 53 fields, and 52 methods registered for JNI access
1 native library: version
[3/7] Building universe... (1.4s @ 0.80GB)
[4/7] Parsing methods... [] (1.0s @ 1.53GB)
[5/7] Inlining methods... [
] (0.8s @ 0.46GB)
[6/7] Compiling methods... [
] (5.5s @ 1.06GB)
[7/7] Creating image... (1.7s @ 1.43GB)
4.45MB (38.22%) for code area: 7,449 compilation units
6.95MB (59.66%) for image heap: 90,863 objects and 5 resources
252.45KB ( 2.12%) for other data
11.64MB in total

Top 10 packages in code area: Top 10 object types in image heap:
664.30KB java.util 928.95KB byte[] for code metadata
360.01KB java.lang 853.94KB java.lang.String
353.50KB com.oracle.svm.jni 840.00KB byte[] for general heap data
225.12KB java.util.regex 637.97KB java.lang.Class
222.22KB java.text 526.25KB byte[] for java.lang.String
207.03KB java.util.concurrent 389.16KB java.util.HashMap$Node
131.93KB com.oracle.svm.core.code 352.09KB char[]
117.02KB java.math 241.23KB com.oracle.svm.core.hub.DynamicHubCompanion
110.77KB com.oracle.svm.core.genscavenge 191.59KB java.util.HashMap$Node[]
99.46KB sun.text.normalizer 163.05KB java.lang.String[]
1.96MB for 109 more packages 1.41MB for 777 more object types

                    0.9s (3.4% of total time) in 17 GCs | Peak RSS: 3.19GB | CPU load: 7.38

Produced artifacts:
D:\test\helloreflection.build_artifacts.txt (txt)
D:\test\helloreflection.exe (executable)

Finished generating 'helloreflection' in 25.0s.
Warning: Image 'helloreflection' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).

d:\test>

但是如果我们通过native-image运行,则会出现问题,这里我们需要加上--no-fallback参数来构建,否则graalvm检测到这个程序使用了未配置的反射时,会把产物自动降级成jvm运行:

代码语言:javascript
复制
$ ./helloreflection foo
Exception runningfoo: NoSuchMethodException

可以看到,运行foo方法提示 NoSuchMethodException,这就是因为在编译时我们无法知道用户真正调用的会是哪个方法,因此静态编译的时候就不会把foo、bar这两个方法认为是“可达的”,最终的native image中也就不会包括这两个方法的机器码 。要解决这个问题,我们就需要进行 配置化的 提前声明

在编译的目录下新建一个reflect-config.json,格式内容如下:

代码语言:javascript
复制
[
]
}
]

这样就相当于显示地声明了HelloReflection的foo方法会被反射调用,native build的时候就会将这个方法编译为机器码并写入image当中。可以再看下运行效果:

编译日志

代码语言:javascript
复制
[7/7] Creating image...                                                                                  (1.6s @ 0.96GB)
4.28MB (37.44%) for code area: 7,124 compilation units
6.91MB (60.45%) for image heap: 89,515 objects and 5 resources
246.55KB ( 2.11%) for other data
11.44MB in total

Top 10 packages in code area: Top 10 object types in image heap:
635.07KB java.util 892.45KB byte[] for code metadata
353.50KB com.oracle.svm.jni 840.44KB java.lang.String
324.45KB java.lang 831.75KB byte[] for general heap data
225.12KB java.util.regex 588.85KB java.lang.Class
222.22KB java.text 516.40KB byte[] for java.lang.String
166.94KB java.util.concurrent 389.16KB java.util.HashMap$Node
131.93KB com.oracle.svm.core.code 352.09KB char[]
117.02KB java.math 231.60KB com.oracle.svm.core.hub.DynamicHubCompanion
110.77KB com.oracle.svm.core.genscavenge 191.59KB java.util.HashMap$Node[]
99.46KB sun.text.normalizer 160.39KB java.lang.String[]
1.90MB for 110 more packages 1.39MB for 750 more object types

                    0.8s (3.4% of total time) in 17 GCs | Peak RSS: 3.24GB | CPU load: 7.12

Produced artifacts:
d:\test\helloreflection.build_artifacts.txt (txt)
d:\test\helloreflection.exe (executable)

Finished generating 'helloreflection' in 23.2s.

运行效果

代码语言:javascript
复制
# linux
$native-image -H:ReflectionConfigurationFiles=./reflect-config.json HelloReflection

$./helloreflection foo
Running foo

$./helloreflection bar
Exception runningbar: NoSuchMethodException

win10

d:\test>helloreflection.exe

d:\test>helloreflection.exe foo
Running foo

d:\test>helloreflection.exe foo bar
Running foo
Exception runningbar: NoSuchMethodException

d:\test>

可以看到,显示声明了的foo方法可以正常被调用,但是没有声明过的bar方法,依然会抛出NoSuchMethodException。

Spring Native

上面我们的实践都是比较简单的针对某一个Java Class而言,而我们实际线上使用的工程往往都要复杂许多,尽管Native Image也提供了编译一个完整jar包的能力,但是对于我们通常使用的spring、maven工程来说,由于反射和代理的存在,根本不可能直接通过Native Image编译成功,因此我们还需要工程框架层面的支持,否则Native Image永远无法成为一种生产工具,而更像一个玩具。

作为Java工程界的龙头大佬,Spring自然观察到了这一点,于是就有了Spring Native。

首先需要说明一下,Spring Native目前还属于实验特性,最新Beta版本为0.12.1,还没有推出稳定的1.0版本(按照官方预期是2022年内会推出),需要Spring Boot最低版本是2.6.6,后续Spring Boot 3.0中也会默认支持Native Image。

https://github.com/spring-projects-experimental/spring-native

可以看到活跃度还是不错的,现在处于适配和扩展的阶段。

那么,Spring Native给我们带来了什么呢?

首先是Spring框架的Native化支持,包括IOC、AOP等各种Spring组件及能力的Native支持;其次是Configuration支持,允许通过@NativeHint注解来动态生成Native Image Configuration(reflect-config.json, proxy-config.json等);最后就是Maven Plugin,可以通过Maven构建获得Native Image,而不需要再手动去执行native-image命令。

可以参考这篇《Spring Native在Windows环境使用说明

https://blog.csdn.net/penker_zhao/article/details/124366910

手动支持

思路就是先打成jar包,然后native-image -cp spring-native-example-0.0.1-SNAPSHOT.jar 生成二进制文件

工程支持

接下来我们通过一个DEMO来简单入门Spring Native

首先确保Spring Boot的版本在2.6.6以上,然后在一个基础Spring Boot项目的基础上,引入以下依赖:

代码语言:javascript
复制

<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.11.4</version>
</dependency>

接着引入plugin

代码语言:javascript
复制
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.11.4</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
</executions>
</plugin>

最后指定native build的profile

代码语言:javascript
复制
<profiles>
<profile>
<id>native</id>
<dependencies>
<!-- Required with Maven Surefire 2.x -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.11</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>build</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<!-- ... -->
</configuration>
</plugin>
<!-- Avoid a clash between Spring Boot repackaging and native-maven-plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

引入之后,运行mvn -Pnative -DskipTests clean package命令,就可以进入native build过程,编译完成后产物在 target/{your-app-name}。

可以看下启动运行的效果:

相比于普通JVM方式运行,启动速度大约提升了5倍(1.2s -> 0.2s)。

对于大部分简单的Spring Boot应用,只需要经过上述这些简单的配置就可以完整运行了,看起来似乎很美好,是不是?但这仅仅是对于Spring组件而言,Native Image目前需要面对的最大问题,还是来自于Java世界数以万计的各种库:Netty、fastjson、logback、junit ...... 尽管很多的开源库都开始改造以支持Native Build,但对于生产环境的企业级应用来说,依然还有很长的路要走(当然,这可能也不是Native Image最适用的场景)。

小结

最后对Java的AOT方案做一个总结。Java AOT在经过一波三折的发展后,目前最为成熟可行的方案就是 GraalVM Native Image,它所带来的优势是显著的:更快的启动速度、更小的内存消耗、脱离JVM独立运行 。但对应的,它也存在着非常多的限制,尤其是在充满了反射等动态特性的Java工程生态圈,很难得到大规模的广泛应用。

总的来说,Java AOT目前是有着明确使用场景的一种技术,主要可以应用于:

  1. 编写命令行CLI程序,希望程序可以完整独立运行而不是需要额外安装JVM。
  2. 运行环境资源严重受限的场景,例如IoT设备、边缘计算等场景。
  3. 希望追求极致的启动速度,并且应用逻辑相对足够轻量,如FaaS。

当然未来Java AOT仍然会进一步发展,我们可以拭目以待。说不定能和go扳手腕就看这个

附录

《graal vm 22.1版本官方文档》

https://www.graalvm.org/22.1/docs

《Spring Native官方文档》

https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#overview