基于GraalVM实现AOT启动SpringBoot项目

1. 安装GraalVM 需要使用GraalVM,需要在Oracle官网下载支持GraalVM的JDK,https://www.oracle.com/java/technologies/downloads/ 。 下载完成之后需要配置环境变量GRAALVM_HOME,指

1. 安装GraalVM

需要使用GraalVM,需要在Oracle官网下载支持GraalVM的JDK,https://www.oracle.com/java/technologies/downloads/

下载完成之后需要配置环境变量GRAALVM_HOME,指向GraalVM的家目录,后面Native包的编译过程当中会是用到GRAALVM_HOME环境变量。

export GRAALVM_HOME=/home/to/graalvm-jdk-22.0.2+9.1/Contents/Home

2. 基于Gradle新建SpringBoot工程

Gradle版本使用最新的8.8版本,Gradle相关的配置使用依赖如下:

plugins {  
  kotlin("jvm") version "1.9.24"  
  kotlin("plugin.spring") version "1.9.24"  
  id("org.springframework.boot") version "3.3.2"  
  id("io.spring.dependency-management") version "1.1.6"  
  id("org.graalvm.buildtools.native") version "0.10.2"  
}  
  
group = "com.wanna.project"  
version = "0.0.1-SNAPSHOT"  
  
java {  
  toolchain {  
  languageVersion = JavaLanguageVersion.of(21)  
    }  
}  
  
repositories {  
  mavenCentral()  
}  
  
dependencies {  
  implementation("org.springframework.boot:spring-boot-starter-web")  
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")  
    implementation("org.jetbrains.kotlin:kotlin-reflect")  
    testImplementation("org.springframework.boot:spring-boot-starter-test")  
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")  
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")  
}  
  
kotlin {  
  compilerOptions {  
  freeCompilerArgs.addAll("-Xjsr305=strict")  
    }  
}  
  
tasks.withType<Test> {  
  useJUnitPlatform()  
}

这里用到了核心的id("org.graalvm.buildtools.native") version "0.10.2"这个插件,这个插件是用于生成Gradle任务的,用于方便给我们在Gradle当中进行GraalVM AOT的二进制机器码的编译。

项目当中只有如下两个类

@SpringBootApplication  
class Application  
  
fun main(args: Array<String>) {  
    runApplication<Application>(*args)  
}

@RestController  
class HelloController {  
  
    @RequestMapping("/hello")  
    fun hello(): String {  
        return "OK"  
  }  
}

打成FatJar包,发现启动时长在1s左右。

2024-08-20T01:45:01.910+08:00  INFO 49663 --- [com.wanna.project.native] [           main] c.w.project.nativeproject.ApplicationKt  : Started ApplicationKt in 0.985 seconds (process running for 1.256)

查看打成的SpringBoot的FatJar包大小,大概在25.6M。

-rw-r--r--   1 ...  staff  25606167 Aug 20 01:46 nativeproject-0.0.1-SNAPSHOT.jar

3.尝试对自己的项目使用GraalVM进行AOT编译

3.1 执行GraalVM AOT编译

安装好JDK之后,在自己的项目下,可以使用如下的命令进行本地的GraalVM的AOT编译:

./gradlew nativeCompile

接着就遇到了第一个问题:

Determining GraalVM installation failed with message: 'gu' at '.../jdk-21.0.3.jdk/Contents/Home/bin/gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.

有可能是以下的几种情况:

  • 1.下载的并不是支持GraalVM的JDK。
  • 2.环境变量配置有问题,比如出现下面的情况,就得观察提示的路径是否是预期的GraalVM配置。

如果正常的话,应该是会有类似如下的这样的编译进度(编译会很慢,需要耐心等待)。

> Task :processAot

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.2)

2024-08-20T02:33:40.810+08:00  INFO 49347 --- [com.wanna.project.native] [           main] c.w.project.nativeproject.ApplicationKt  : Starting ApplicationKt using Java 21.0.4 with PID 49347 (/Users/jianchaojia/Desktop/Code/java/project/com.wanna.project.native/build/classes/kotlin/main started by jianchaojia in /Users/jianchaojia/Desktop/Code/java/project/com.wanna.project.native)
2024-08-20T02:33:40.812+08:00  INFO 49347 --- [com.wanna.project.native] [           main] c.w.project.nativeproject.ApplicationKt  : No active profile set, falling back to 1 default profile: "default"

> Task :generateResourcesConfigFile
[native-image-plugin] Resources configuration written into /Users/jianchaojia/Desktop/Code/java/project/com.wanna.project.native/build/native/generated/generateResourcesConfigFile/resource-config.json

> Task :nativeCompile
[native-image-plugin] GraalVM Toolchain detection is disabled
[native-image-plugin] GraalVM location read from environment variable: GRAALVM_HOME
[native-image-plugin] Native Image executable path: /Users/jianchaojia/Desktop/Code/jdk/graalvm-jdk-22.0.2+9.1/Contents/Home/lib/svm/bin/native-image
Warning: The option '-H:ResourceConfigurationResources=META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-core/tomcat-resource.json' is experimental and must be enabled via '-H:+UnlockExperimental

执行完成之后,会有如下的提示,编译成功,且编译时长花费了1分42秒。

Build artifacts:
 /Users/jianchaojia/Desktop/Code/java/project/com.wanna.project.native/build/native/nativeCompile/nativeproject (executable)
========================================================================================================================
Finished generating 'nativeproject' in 1m 42s.
[native-image-plugin] Native Image written to: /Users/jianchaojia/Desktop/Code/java/project/com.wanna.project.native/build/native/nativeCompile

提示你可以在build/native/nativeCompile/目录下找到二进制文件,我们可以找到该文件,像普通软件一样只需要双击即可执行。

双击之后,会有如下的启动SpringBoot的过程,和正常的SpringBoot启动没什么区别

$ build/native/nativeCompile/nativeproject 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.2)

2024-08-20T02:38:45.591+08:00  INFO 49501 --- [com.wanna.project.native] [           main] c.w.project.nativeproject.ApplicationKt  : Starting AOT-processed ApplicationKt using Java 22.0.2 with PID 49501

......
2024-08-20T02:38:45.630+08:00  INFO 49501 --- [com.wanna.project.native] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2024-08-20T02:38:45.630+08:00  INFO 49501 --- [com.wanna.project.native] [           main] c.w.project.nativeproject.ApplicationKt  : Started ApplicationKt in 0.057 seconds (process running for 0.081)

启动只需要0.05s,启动速度相比使用FatJar的方式提升了20倍。

我们查看该文件,发现该二进制包在Macos下大小为92.3M,对比Jar包的大小25.6M,包的大小翻了大约3.6倍左右。

$ ls -al build/native/nativeCompile/              
total 180464
drwxr-xr-x  3 jianchaojia  staff        96 Aug 20 02:35 .
drwxr-xr-x  5 jianchaojia  staff       160 Aug 20 02:33 ..
-rwxr-xr-x  1 jianchaojia  staff  92394384 Aug 20 02:35 nativeproject

3.2 IDEA的Gradle无法识别到GRAALVM_HOME环境变量

我在本地通过gradlew命令手动执行能通过,但是我尝试通过Gradle当中运行时,一直找不到我配置的GRAALVM_HOME环境变量,一直提示我使用的JDK不是GRAALVM的JDK,是别的JDK路径。

一直提示下面的内容,具体原因不知道。

Determining GraalVM installation failed with message: 'gu' at '.../jdk-21.0.3.jdk/Contents/Home/bin/gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.

接着,我尝试Debug Gradle的编译过程,我通过System.getenv尝试去获取这个环境变量,发现确实没这个环境变量。

gradle-debug-graalvm-env.png

接着我尝试通过修改IDEA的Gradle的运行配置,去手动添加环境变量:

GRAALVM_HOME=/Users/jianchaojia/Desktop/Code/jdk/graalvm-jdk-22.0.2+9.1/Contents/Home

gradle-debug-graalvm-env-gradle-runtime-setting.png

再次尝试进行nativeCompile编译,发现IDEA的Gradle AOT编译正常。

gradle-graalvm-compile-env-gradle-runtime-setting.png.png

为什么IDEA启动的Gradle没有GRAALVM_HOME环境变量?因为IDEA启动的进程会继承IDEA进程的环境变量,而IDEA会继承操作系统的环境变量,但是通过查看IDEA继承的环境变量发现,继承操作系统的环境变量时就缺少了GRAALVM_HOME。

idea-env.jpg

所以这是因为IDEA没有继承环境变量导致的,那么为什么没继承呢?因为JAVA进程在启动之后,环境变量就被缓存下来了,是不可变的,如果在IDEA运行中更改环境变量,那么对IDEA是不生效的,所以如果修改了环境变量,需要重启IDEA才能生效。

重启IDEA之后,重新查看环境变量生效。

idea-env-graalvm.png

Comment