ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

GraalVM 21 Native Image 编译踩坑全记录:从 class 初始化冲突到成功构建 环境信息项目版本/信息GraalVM21.0.127-LTS (Oracle GraalVM)Spring Boot3.4.5native-maven-plugin0.10.6操作系统Windows 25H2C 编译器cl.exe (Microsoft x64 19.51.36256)项目背景Spring Boot 3.x 智能客服项目集成了 Nacos配置中心/服务注册、Milvus向量数据库、Logback日志、MyBatis-Plus、Elasticsearch、Redis 等组件。配置了 Maven 双 ProfileJVM / Native支持 GraalVM AOT 编译。编译命令是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_TIMEClassInitializationConfiguration.insertRec()方法报 Incompatible change of initialization policy。排查过程最初以为是 Nacos 独有来源排除nacos-client后发现grpc-netty-shaded仍在 classpath 上mvn dependency:tree -Pnative -Dincludesio.grpc:grpc-netty-shaded发现第二条依赖链langchain4j-milvus → milvus-sdk-java → grpc-netty-shaded:1.59.1最终方案将两个冲突源的依赖 scope 设为 provided编译可用native-image 运行时不在 classpathprofile idnative/id dependencies !-- Nacos: grpc-netty-shaded via nacos-client -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId scopeprovided/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId scopeprovided/scope /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-bootstrap/artifactId scopeprovided/scope /dependency !-- Milvus: grpc-netty-shaded via milvus-sdk-java -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId scopeprovided/scope /dependency /dependencies /profile注意不能简单用exclusions排除nacos-client因为spring-cloud-starter-alibaba-nacos-logging子模块会引用nacos-client的NacosLogging类导致NoClassDefFoundError。providedscope 是更干净的方案。第二阶段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中添加buildArg--initialize-at-build-timech.qos.logback.**,org.slf4j.**,org.apache.commons.logging.**/buildArg结果完全无效同样的错误。踩坑 2*通配符也不生效改为单星号buildArg--initialize-at-build-timech.qos.logback.*,org.slf4j.*,org.apache.commons.logging.*/buildArg结果同样无效。根因GraalVM 21 的通配符语义GraalVM 21 的ClassInitializationConfiguration使用startsWith 边界检查的匹配逻辑。**和*在包名模式中的行为与直觉不符——它们并不像 Ant 风格 glob 那样工作。经过实测验证GraalVM 21 的通配符对这些类不生效必须使用显式类名。最终方案显式列出所有 11 个类buildArg--initialize-at-build-timech.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 相关类冲突连环坑错误 1ProxyingConsole 堆对象错误Error: An object of type java.io.ProxyingConsole was found in the image heap.Trace: Object was reached bytrying to constant fold static field sun.security.util.Password$ConsoleHolder.c1builder 分析阶段尝试常量折叠Password$ConsoleHolder.c1静态字段该字段持有ProxyingConsole对象。但ProxyingConsole被标记为 RUN_TIME不允许出现在 image heap 中。尝试修复添加--initialize-at-build-timejava.io.ProxyingConsole错误 2Console 继承链冲突Error: Incompatible change of initialization policy for java.io.Console:trying to change BUILD_TIME super type of java.io.ProxyingConsole to RUN_TIMEfor JDK native code support via JNIProxyingConsole 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-timesun.security.util.Password$ConsoleHolder后Class initialization of jdk.internal.io.JdkConsoleImpl$1ConsoleHolder failed.java.lang.IllegalStateException: Cant create a dedicated password consolesince a real console already existsPassword$ConsoleHolder的clinit触发了JdkConsoleImpl$1ConsoleHolder的初始化后者在构建环境中失败没有可用的专用密码控制台。最终方案同时标记两个 JDK 内部类为 RUN_TIMEbuildArg--initialize-at-run-timejdk.internal.io.JdkConsoleImpl$1ConsoleHolder,sun.security.util.Password$ConsoleHolder/buildArgPassword$ConsoleHolder→ RUN_TIME阻止构建时创建ProxyingConsole对象JdkConsoleImpl$1ConsoleHolder→ RUN_TIME阻止级联初始化崩溃结果构建成功通过最终配置总览pom.xml native profile 关键配置profile idnative/id dependencies !-- grpc-netty-shaded 冲突源provided scope 排除 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId scopeprovided/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId scopeprovided/scope /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-bootstrap/artifactId scopeprovided/scope /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId scopeprovided/scope /dependency /dependencies build plugins plugin groupIdorg.graalvm.buildtools/groupId artifactIdnative-maven-plugin/artifactId version0.10.6/version extensionstrue/extensions executions execution idbuild-native/id goalsgoalcompile/goal/goals phasepackage/phase /execution execution iddefault-cli/id goalsgoalcompile/goal/goals phasepackage/phase configuration buildArgs buildArg--initialize-at-build-timech.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-timejdk.internal.io.JdkConsoleImpl$1ConsoleHolder,sun.security.util.Password$ConsoleHolder/buildArg /buildArgs /configuration /execution /executions configuration buildArgs buildArg--initialize-at-build-timech.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-timejdk.internal.io.JdkConsoleImpl$1ConsoleHolder,sun.security.util.Password$ConsoleHolder/buildArg /buildArgs /configuration /plugin /plugins /build /profilenative-image.propertiesArgs -H:IncludeResources.*\\.xml$|.*\\.yml$|.*\\.properties$|.*\\.md$|.*\\.sql$ \ --no-fallback \ -H:ReportExceptionStackTraces踩坑经验总结#坑教训1grpc-netty-shaded的 RUN_TIME 声明不可覆盖GraalVM 21 硬性阻止 RUN_TIME → BUILD_TIME 的策略变更唯一方案是排除依赖2**通配符在 GraalVM 21 中不生效ClassInitializationConfiguration使用startsWith匹配**被当作字面字符3*通配符同样不生效单星号也不匹配子包中的类必须使用显式类名4--initialize-at-build-timeProxyingConsole与 JDK JNI 冲突ProxyingConsole extends Console父类Console被 JDK 原生注册锁定为 RUN_TIME5只标记Password$ConsoleHolder为 RUN_TIME 会级联崩溃其clinit触发JdkConsoleImpl$1ConsoleHolder初始化必须两者同时标记6XML 注释中不能包含--pom.xml 的 XML 注释中写--initialize-at-run-time会导致解析错误7providedscope 优于exclusions排除传递依赖的子模块可能引用被排除 jar 的类provided更干净Native 模式限制功能JVM 模式Native 模式Nacos 配置中心/服务注册✅❌ (使用本地 application.yml)Milvus 向量存储✅❌ (provided scope 排除)其他所有功能✅✅希望这篇记录能帮助遇到类似问题的同学。GraalVM Native Image 的编译错误排查核心思路仔细阅读错误信息中的 trace 和 suggestion它们通常已经给出了精确的修复方向
返回列表