GraalVM AOT 踩坑记录
GraalVM AOT 踩坑记录
因为最近对GraalVM的AOT花了比较多的精力折腾(主要是想方设法把小雨妙享进行AOT),也稍微摸到了一些门路,遂写一篇文章记录下来。
本文主要包括两大部分:
- SpringBoot 3.x配合Maven的AOT(可能也适用于多数不含用户界面的程序)
- Compose Desktop配合Gradle的AOT(可能也适用于多数基于AWT的程序)
至于环境变量配置和GraalVM安装之类的问题不在讨论范围内
通用部分
AOT之后的程序有着内存占用低、启动速度快等良好特性,对云原生和客户端应用有着比较大的意义,不过现在AOT后GC只能支持到G1,并且在高并发下性能也不如JVM来得高,但作为一种技术探索也是不错的。
此次主要讨论Windows 64位平台,其余平台类似。不论你是编译上述哪种程序都需要使用到 x64 Native Tools Command Prompt ,这个工具在安装了Visual Studio后(具体是哪个组件里的记不清了)会出现在你的开始菜单里,以下内容均使用该工具在项目根目录执行指令。
众所周知对于GraalVM的AOT,最头疼的莫过于元数据,好在Graal提供了Agent工具,可以自动跟踪并生成对应的元数据,只需要简单使用下面指令运行你的程序即可,运行程序时尽量将所有分支都走一遍,让Agent能尽量完善地追踪。
Spring Boot 3.x Graal AOT 踩坑记录
编译指令
mvn native:compile
cl.exe 找不到
安装MSVC
后将以下目录加入Path
环境变量
D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\bin\Hostx64\x64
将以下目录创建并加入LIB
环境变量
D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\lib\x64
将以下目录加入INCLUDE
环境变量
D:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\include
stdio.h 报错
使用x64 Native Tools Command Prompt for VS 2022
在项目根目录运行以下指令即可
.\mvnw -Pnative clean native:compile
Error: Classes that should be initialized at run time got initialized during image building:
该错误是由于有类没能在构建期间被 GraalVM 找到,需要手动使用--initialize-at-build-time=
指定,例如下面这个 SLF4J 的错误:
Error: Classes that should be initialized at run time got initialized during image building:
org.slf4j.LoggerFactory was unintentionally initialized at build time. To see why org.slf4j.LoggerFactory got initialized use --trace-class-initialization=org.slf4j.LoggerFactory
这时候需要在pom.xml
中的<plugins>
下名字为org.graalvm.buildtools
的插件里加上--initialize-at-build-time=
这里加上前为:
<build>
<plugins>
...
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
...
</plugins>
</build>
加上后:
<build>
<plugins>
...
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs>
--no-fallback
--initialize-at-build-time=org.slf4j.LoggerFactory
--initialize-at-build-time=org.slf4j.simple.SimpleLogger
--initialize-at-build-time=org.slf4j.MDC
--initialize-at-build-time=ch.qos.logback.classic.Level
--initialize-at-build-time=ch.qos.logback.classic.Logger
--initialize-at-build-time=ch.qos.logback.core.util.StatusPrinter
--initialize-at-build-time=ch.qos.logback.core.status.StatusBase
--initialize-at-build-time=ch.qos.logback.core.status.InfoStatus
--initialize-at-build-time=ch.qos.logback.core.spi.AppenderAttachableImpl
--initialize-at-build-time=org.slf4j.LoggerFactory
--initialize-at-build-time=ch.qos.logback.core.util.Loader
--initialize-at-build-time=org.slf4j.impl.StaticLoggerBinder
--initialize-at-build-time=ch.qos.logback.classic.spi.ThrowableProxy
--initialize-at-build-time=ch.qos.logback.core.CoreConstants
-H:+ReportExceptionStackTraces
</buildArgs>
</configuration>
</plugin>
...
</plugins>
</build>
application.yml
没有打包
在<build>
-><plugins>
->artifactId
为native-maven-plugin
的<plugin>
->configuration
-><buildArgs>
加上
-H:IncludeResources=".*/application.yml$"
启动报错 Error creating bean with name 'lettuceClientResources': Instantiation of supplied bean failed
spring-boot-starter-data-redis
依赖导入由:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
改为
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
启动报错 org.apache.catalina.LifecycleException: Failed to stop componen [StandardEngine[Tomcat].StandardHost
pom.xml
删除log4j-api
依赖即可
Compose Desktop
编译指令
.\gradlew -Pagent run # 使用agent自动添加元数据
.\gradlew metadataCopy --task run --dir src\main\resources\META-INF\native-image
.\gradlew nativeBuild
Gradle配置
build.gradle.kts
plugins {
id("org.graalvm.buildtools.native") version "0.10.2"
}
graalvmNative {
toolchainDetection.set(false)
binaries{
named("main"){
mainClass.set("MainKt")
imageName.set("TyuShare")
buildArgs("-O4", "-H:+AddAllCharsets")
}
}
agent{
metadataCopy {
mergeWithExisting.set(true)
}
}
}
Cannot open (null)\bin\jawt.dll
原因:AWT找不到 java.home
环境变量,导致找不到 jawt.dll
解决办法:将native编译后的所有dll放置在 <程序根目录>/bin
,然后启动程序时附带参数-Djava.home=.
,或是在main方法中设置,如下:
fun main() {
if (System.getProperty("java.home") == null) {
System.setProperty("java.home", ".")
}
application {
...
}
}