GraalVM AOT 踩坑记录

GraalVM AOT 踩坑记录

因为最近对GraalVM的AOT花了比较多的精力折腾(主要是想方设法把小雨妙享进行AOT),也稍微摸到了一些门路,遂写一篇文章记录下来。

本文主要包括两大部分:

  1. SpringBoot 3.x配合Maven的AOT(可能也适用于多数不含用户界面的程序)
  2. 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>->artifactIdnative-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 {
    ...
    }
}