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-clientNacosLogging 类,导致 NoClassDefFoundErrorprovided 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.xmlbuildArgs 中添加:

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
,它们通常已经给出了精确的修复方向

相关推荐
2601_954811827 小时前
AI智能教育多智能体协同:备教辅全链路架构实现 — 技术架构
ai
Java后端的Ai之路7 小时前
Python进阶探索17 - Python中的深拷贝与浅拷贝
人工智能·python·ai·浅拷贝·深拷贝
龙智DevSecOps解决方案8 小时前
AI驱动的知识管理指南:基于Atlassian Intelligence、Rovo、Teamwork Collection打造团队知识引擎
ai·atlassian·需求管理·团队协作·知识管理
李燚8 小时前
Agent 要用 API key,但明文一次都不能进模型——Secret Runtime 落地实录(第110篇)
ai·agent·credential·secret·eino·deepflux·secret runtime
奇牙coding8 小时前
gpt-5.6-luna 接入踩坑实录:model_not_found 排查与 reasoning_summary 字段使用说明
ai
霸道流氓气质8 小时前
Prompt 工程最佳实践与企业级模板库建设
ai
海带紫菜菠萝汤9 小时前
本周 AI 观察:嘴上喊着降速,手上全踩油门
人工智能·深度学习·ai·开源·大模型
全栈技术负责人9 小时前
让 Agent 动手前先“听懂人话”:意图识别插件的设计与实践
ai·ai编程
anxiao_m9 小时前
2026AI大模型API统一接入平台测评:4款主流产品横向对比
ai·aigc
Experience-摆渡10 小时前
开源RAG知识库WeKnora深度调研:让知识自己长成体系
爬虫·docker·开源·rag