GraalVM 21 Native Image 编译踩坑全记录:从 class 初始化冲突到成功构建

项目 版本/信息
GraalVM 21.0.12+7-LTS (Oracle GraalVM)
Spring Boot 3.4.5
native-maven-plugin 0.10.6
操作系统 Windows 25H2
C 编译器 cl.exe (Microsoft x64 19.51.36256)
[环境信息]

项目背景

Spring Boot 3.x 智能客服项目,集成了 Nacos(配置中心/服务注册)、Milvus(向量数据库)、Logback(日志)、MyBatis-Plus、Elasticsearch、Redis 等组件。配置了 Maven 双 Profile(JVM / Native)支持 GraalVM AOT 编译。


编译命令是:

bash 复制代码
mvn clean -Pnative native:compile -X

第一阶段:grpc-netty-shaded 类初始化冲突(不可调和)

错误现象

Error: Classes that should be initialized at run time got initialized during image building:

io.grpc.netty.shaded.io.netty.util.AbstractReferenceCounted was unintentionally initialized at build time.

...(27+ 个 netty 类)

根因分析

Nacos 2.x 通过 nacos-client 传递依赖 grpc-netty-shaded,该 jar 内嵌 META-INF/native-image/native-image.properties 声明了所有 netty 类为 --initialize-at-run-time。但 native-image builder 的 points-to 分析阶段会通过 ensureClassInitialized 强制在构建时初始化这些类,两者不可调和。

GraalVM 21 硬性阻止从 RUN_TIME 改为 BUILD_TIME(ClassInitializationConfiguration.insertRec() 方法),报 "Incompatible change of initialization policy"。

排查过程

最初以为是 Nacos 独有来源,排除 nacos-client 后发现 grpc-netty-shaded 仍在 classpath 上:

bash 复制代码
mvn dependency:tree -Pnative -Dincludes=io.grpc:grpc-netty-shaded

发现第二条依赖链:langchain4j-milvus → milvus-sdk-java → grpc-netty-shaded:1.59.1

最终方案

将两个冲突源的依赖 scope 设为 provided(编译可用,native-image 运行时不在 classpath):

XML 复制代码
<profile>
    <id>native</id>
    <dependencies>
        <!-- Nacos: grpc-netty-shaded via nacos-client -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- Milvus: grpc-netty-shaded via milvus-sdk-java -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-milvus</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</profile>

注意 :不能简单用 <exclusions> 排除 nacos-client,因为 spring-cloud-starter-alibaba-nacos-logging 子模块会引用 nacos-client 的 NacosLogging 类,导致 NoClassDefFoundError。provided scope 是更干净的方案。

第二阶段:Logback/SLF4J 类初始化冲突(通配符陷阱)

错误现象

排除 grpc-netty-shaded 后,构建继续报错:

Error: Classes that should be initialized at run time got initialized during image building:

ch.qos.logback.core.status.StatusBase was unintentionally initialized at build time.

ch.qos.logback.classic.Logger was unintentionally initialized at build time.

org.slf4j.LoggerFactory was unintentionally initialized at build time.

org.apache.commons.logging.LogFactory was unintentionally initialized at build time.

...(共 11 个类)

踩坑 1:** 通配符不生效

首先在 pom.xml 的 buildArgs 中添加:

XML 复制代码
<buildArg>--initialize-at-build-time=ch.qos.logback.**,org.slf4j.**,org.apache.commons.logging.**</buildArg>

结果:完全无效,同样的错误。

踩坑 2:* 通配符也不生效

改为单星号:

XML 复制代码
<buildArg>--initialize-at-build-time=ch.qos.logback.*,org.slf4j.*,org.apache.commons.logging.*</buildArg>

结果:同样无效。

根因:GraalVM 21 的通配符语义

GraalVM 21 的 ClassInitializationConfiguration 使用 startsWith + 边界检查的匹配逻辑。** 和 * 在包名模式中的行为与直觉不符 ------它们并不像 Ant 风格 glob 那样工作。经过实测验证,GraalVM 21 的通配符对这些类不生效,必须使用显式类名。

最终方案:显式列出所有 11 个类

XML 复制代码
<buildArg>--initialize-at-build-time=ch.qos.logback.core.status.StatusBase,ch.qos.logback.core.status.InfoStatus,ch.qos.logback.core.util.StatusPrinter,ch.qos.logback.core.util.StatusPrinter2,ch.qos.logback.core.util.Loader,ch.qos.logback.classic.Logger,ch.qos.logback.classic.Level,org.slf4j.LoggerFactory,org.slf4j.helpers.Reporter,org.apache.commons.logging.LogFactory,org.apache.commons.logging.impl.Jdk14Logger</buildArg>

结果:类初始化错误全部消失!构建进入下一阶段。


第三阶段:JDK Console 相关类冲突(连环坑)

错误 1:ProxyingConsole 堆对象错误

Error: An object of type 'java.io.ProxyingConsole' was found in the image heap.

Trace: Object was reached by

trying to constant fold static field sun.security.util.Password$ConsoleHolder.c1

builder 分析阶段尝试常量折叠 Password$ConsoleHolder.c1 静态字段,该字段持有 ProxyingConsole 对象。但 ProxyingConsole 被标记为 RUN_TIME,不允许出现在 image heap 中。

尝试修复 :添加 --initialize-at-build-time=java.io.ProxyingConsole

错误 2:Console 继承链冲突

Error: Incompatible change of initialization policy for java.io.Console:

trying to change BUILD_TIME super type of java.io.ProxyingConsole to RUN_TIME

for JDK native code support via JNI

ProxyingConsole extends Console,而 GraalVM 21 的 JDK JNI 注册要求 java.io.Console 保持 RUN_TIME。强制 ProxyingConsole 为 BUILD_TIME 会级联要求父类 Console 也变为 BUILD_TIME,与 JDK 原生注册冲突。

结论 :不能将 ProxyingConsole 设为 BUILD_TIME,必须从源头阻止对象创建。

错误 3:级联初始化失败

改为 --initialize-at-run-time=sun.security.util.Password$ConsoleHolder 后:

Class initialization of jdk.internal.io.JdkConsoleImpl$1ConsoleHolder failed.

java.lang.IllegalStateException: Can't create a dedicated password console

since a real console already exists

Password$ConsoleHolder 的 <clinit> 触发了 JdkConsoleImpl$1ConsoleHolder 的初始化,后者在构建环境中失败(没有可用的专用密码控制台)。

最终方案:同时标记两个 JDK 内部类为 RUN_TIME

XML 复制代码
<buildArg>--initialize-at-run-time=jdk.internal.io.JdkConsoleImpl$1ConsoleHolder,sun.security.util.Password$ConsoleHolder</buildArg>
  • Password$ConsoleHolder → RUN_TIME:阻止构建时创建 ProxyingConsole 对象
  • JdkConsoleImpl$1ConsoleHolder → RUN_TIME:阻止级联初始化崩溃

结果:构建成功通过!


最终配置总览

pom.xml native profile 关键配置

XML 复制代码
<profile>
    <id>native</id>
    <dependencies>
        <!-- grpc-netty-shaded 冲突源:provided scope 排除 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-milvus</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <version>0.10.6</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>build-native</id>
                        <goals><goal>compile</goal></goals>
                        <phase>package</phase>
                    </execution>
                    <execution>
                        <id>default-cli</id>
                        <goals><goal>compile</goal></goals>
                        <phase>package</phase>
                        <configuration>
                            <buildArgs>
                                <buildArg>--initialize-at-build-time=ch.qos.logback.core.status.StatusBase,ch.qos.logback.core.status.InfoStatus,ch.qos.logback.core.util.StatusPrinter,ch.qos.logback.core.util.StatusPrinter2,ch.qos.logback.core.util.Loader,ch.qos.logback.classic.Logger,ch.qos.logback.classic.Level,org.slf4j.LoggerFactory,org.slf4j.helpers.Reporter,org.apache.commons.logging.LogFactory,org.apache.commons.logging.impl.Jdk14Logger</buildArg>
                                <buildArg>--initialize-at-run-time=jdk.internal.io.JdkConsoleImpl$1ConsoleHolder,sun.security.util.Password$ConsoleHolder</buildArg>
                            </buildArgs>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <buildArgs>
                        <buildArg>--initialize-at-build-time=ch.qos.logback.core.status.StatusBase,ch.qos.logback.core.status.InfoStatus,ch.qos.logback.core.util.StatusPrinter,ch.qos.logback.core.util.StatusPrinter2,ch.qos.logback.core.util.Loader,ch.qos.logback.classic.Logger,ch.qos.logback.classic.Level,org.slf4j.LoggerFactory,org.slf4j.helpers.Reporter,org.apache.commons.logging.LogFactory,org.apache.commons.logging.impl.Jdk14Logger</buildArg>
                        <buildArg>--initialize-at-run-time=jdk.internal.io.JdkConsoleImpl$1ConsoleHolder,sun.security.util.Password$ConsoleHolder</buildArg>
                    </buildArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>
</profile>

native-image.properties

java 复制代码
Args = -H:IncludeResources=.*\\.xml$|.*\\.yml$|.*\\.properties$|.*\\.md$|.*\\.sql$ \
       --no-fallback \
       -H:+ReportExceptionStackTraces

踩坑经验总结

# 坑 教训
1 grpc-netty-shaded 的 RUN_TIME 声明不可覆盖 GraalVM 21 硬性阻止 RUN_TIME → BUILD_TIME 的策略变更,唯一方案是排除依赖
2 ** 通配符在 GraalVM 21 中不生效 ClassInitializationConfiguration 使用 startsWith 匹配,** 被当作字面字符
3 * 通配符同样不生效 单星号也不匹配子包中的类,必须使用显式类名
4 --initialize-at-build-time=ProxyingConsole 与 JDK JNI 冲突 ProxyingConsole extends Console,父类 Console 被 JDK 原生注册锁定为 RUN_TIME
5 只标记 Password$ConsoleHolder 为 RUN_TIME 会级联崩溃 其 <clinit> 触发 JdkConsoleImpl$1ConsoleHolder 初始化,必须两者同时标记
6 XML 注释中不能包含 -- pom.xml 的 XML 注释中写 --initialize-at-run-time 会导致解析错误
7 provided scope 优于 <exclusions> 排除传递依赖的子模块可能引用被排除 jar 的类,provided 更干净

Native 模式限制

功能 JVM 模式 Native 模式
Nacos 配置中心/服务注册 ✅ ❌ (使用本地 application.yml)
Milvus 向量存储 ✅ ❌ (provided scope 排除)
其他所有功能 ✅ ✅

希望这篇记录能帮助遇到类似问题的同学。GraalVM Native Image 的编译错误排查核心思路:仔细阅读错误
信息中的 trace 和 suggestion
,它们通常已经给出了精确的修复方向

相关推荐
智码看视界27 分钟前
向量数据库选型实战:Milvus vs Chroma vs Qdrant 谁更适合 RAG
embedding·milvus·向量数据库·chroma·rag·语义检索·选型对比
wflynn2 小时前
Fold:Effect原生多提供商Agent核心框架解读
ai·技术
敲代码的小小酥2 小时前
Agent 运行机制全景:从工具调用到沙箱隔离
ai·agent
欣欣之王来了3 小时前
国外主流大模型深度对比:GPT、Claude、Gemini技术细节与实战选型指南
人工智能·ai·大模型
MicrosoftReactor4 小时前
技术速递|使用 GitHub Security Lab Taskflow Agent 实现 AI 驱动的模糊测试
人工智能·ai·github·copilot·agent·模糊测试·ai-agent
宇擎智脑科技4 小时前
Sirchmunk 深度解析(一):一个无需向量数据库的自进化搜索引擎架构设计
人工智能·rag·sirchmunk
wang_yb4 小时前
Scikit-Learn实战:5步搞定PCA降维
ai·databook
我最爱吃鱼香茄子5 小时前
【毕业设计优选】人力资源管理系统|Java|SpringBoot|Vue|前后端分离|带详细文档+部署教学视频
java·vue·毕业设计·springboot
Anastasiozzzz5 小时前
深度拆解 Jev 模型:扒开“系统一模型”的底层工作原理
ai·语言模型
codigger5 小时前
Redis 正式接入 AI:当"最懂速度的数据库"开始解决"记忆问题"
redis·分布式·后端·ai·向量检索