Spring Boot 3 + GraalVM 23.x 构建的AWS Lambda原生镜像启动时加载本地库触发空指针异常
我最近用Spring Boot 3和GraalVM 23.x构建AWS Lambda的原生镜像,镜像本身构建成功了,但每次尝试调用REST方法时,都会触发启动错误,错误类型是Runtime.ExitError,核心是空指针异常,具体日志如下:
2024-12-30T20:19:53.908Z INIT_REPORT Init Duration: 675.51 ms Phase: init Status: error Error Type: Runtime.ExitError 2024-12-30T20:19:53.905Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNILibraryInitializer.callOnLoadFunction(JNILibraryInitializer.java:70) 2024-12-30T20:19:53.905Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNILibraryInitializer.initialize(JNILibraryInitializer.java:131) 2024-12-30T20:19:53.905Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.NativeLibrarySupport.addLibrary(NativeLibrarySupport.java:128) 2024-12-30T20:19:53.905Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.NativeLibrarySupport.addLibrary(NativeLibrarySupport.java:84) 2024-12-30T20:19:53.905Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.NativeLibraries.loadLibrary0(NativeLibraries.java:147) 2024-12-30T20:19:53.905Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.NativeLibraries.loadLibraryAbsolute(NativeLibraries.java:102) 2024-12-30T20:19:53.905Z at java.base@23.0.1/java.lang.ClassLoader.loadLibrary(ClassLoader.java:116) 2024-12-30T20:19:53.905Z at java.base@23.0.1/java.lang.Runtime.load0(Runtime.java:852) 2024-12-30T20:19:53.905Z at java.base@23.0.1/java.lang.System.load(System.java:2028) 2024-12-30T20:19:53.905Z at com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper.unpackAndLoad(JniHelper.java:51) 2024-12-30T20:19:53.905Z at com.amazonaws.services.lambda.runtime.api.client.runtimeapi.JniHelper.load(JniHelper.java:38) 2024-12-30T20:19:53.905Z at com.amazonaws.services.lambda.runtime.api.client.runtimeapi.NativeClient.init(NativeClient.java:16) 2024-12-30T20:19:53.905Z at com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl.<init>(LambdaRuntimeApiClientImpl.java:41) 2024-12-30T20:19:53.905Z at com.amazonaws.services.lambda.runtime.api.client.AWSLambda.startRuntime(AWSLambda.java:203) 2024-12-30T20:19:53.905Z at com.amazonaws.services.lambda.runtime.api.client.AWSLambda.startRuntime(AWSLambda.java:190) 2024-12-30T20:19:53.905Z ... 1 more 2024-12-30T20:19:53.901Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.functions.JNIFunctions$Support.getFieldID(JNIFunctions.java:1858) 2024-12-30T20:19:53.901Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.functions.JNIFunctions.GetFieldID(JNIFunctions.java:461) 2024-12-30T20:19:53.901Z at org.graalvm.nativeimage.builder/com.oracle.svm.core.jni.JNIOnLoadFunctionPointer.invoke(JNILibraryInitializer.java) 2024-12-30T20:19:53.900Z Caused by: java.lang.NullPointerException 2024-12-30T20:19:53.898Z at com.amazonaws.services.lambda.runtime.api.client.AWSLambda.startRuntime(AWSLambda.java:192) 2024-12-30T20:19:53.898Z at com.amazonaws.services.lambda.runtime.api.client.AWSLambda.main(AWSLambda.java:180) 2024-12-30T20:19:53.894Z Exception in thread "main" java.lang.Error: java.lang.NullPointerException 2024-12-30T20:19:53.319Z LOGS Name: cloudwatch_lambda_agent State: Subscribed Types: [Platform]
在构建镜像前,我已经运行了Native Agent来生成元数据:
c:\projects\graalvm-jdk-23.0.1+11.1\bin\java.exe -agentlib:native-image-agent=config-output-dir=META-INF/native-image -jar kount-worker.jar
并且在构建时使用了生成的reachability-metadata.json文件。
我的Maven配置如下:
<build> <finalName>kount-worker</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.4.1</version> <configuration> <jvmArguments>--enable-preview</jvmArguments> <compilerArguments>--enable-preview</compilerArguments> </configuration> <executions> <execution> <id>process-aot</id> <goals> <goal>process-aot</goal> </goals> </execution> </executions> </plugin> <!-- <plugin>--> <!-- <groupId>org.springframework.boot</groupId>--> <!-- <artifactId>spring-boot-maven-plugin</artifactId>--> <!-- <configuration>--> <!-- <excludes>--> <!-- <exclude>--> <!-- <groupId>org.projectlombok</groupId>--> <!-- <artifactId>lombok</artifactId>--> <!-- </exclude>--> <!-- </excludes>--> <!-- </configuration>--> <!-- <executions>--> <!-- <execution>--> <!-- <goals>--> <!-- <goal>repackage</goal>--> <!-- </goals>--> <!-- </execution>--> <!-- </executions>--> <!-- </plugin>--> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <extensions>true</extensions> <executions> <execution> <id>build-native</id> <goals> <goal>compile-no-fork</goal> </goals> <phase>package</phase> </execution> <execution> <id>test-native</id> <goals> <goal>test</goal> </goals> <phase>test</phase> </execution> </executions> <configuration> <!-- <mainClass>com.eye4fraud.kount.KountApplication</mainClass>--> <mainClass>com.amazonaws.services.lambda.runtime.api.client.AWSLambda</mainClass> <imageName>kount-worker</imageName> <buildArgs> <buildArg>-H:DynamicProxyConfigurationFiles=src/main/resources/proxies.json</buildArg> <buildArg>--emit build-report</buildArg> <buildArg>-Os</buildArg> <buildArg>-H:-ReduceImplicitExceptionStackTraceInformation</buildArg> <buildArg>-H:ResourceConfigurationFiles=src/main/resources/resource-config.json</buildArg> <buildArg>-H:+ReportExceptionStackTraces</buildArg> <buildArg>-H:JNIConfigurationFiles=src/main/resources/jni-config.json</buildArg> </buildArgs> </configuration> </plugin> </plugins> </build>
问题分析与解决方案
从日志来看,空指针发生在Lambda运行时加载JNI本地库的过程中,具体是JNIFunctions$Support.getFieldID尝试获取字段ID时返回了null,这通常是因为原生镜像构建时缺少Lambda运行时JNI相关的类、字段或方法的元数据配置,导致GraalVM没有将这些元素包含在镜像中。
以下是几个可以尝试的解决步骤:
1. 确保Native Agent完整捕获Lambda运行时的JNI调用
你当前运行Agent的方式只是启动了Spring Boot应用,但没有模拟Lambda的实际调用流程。Lambda的入口是AWSLambda类,它会初始化JNI库,这部分逻辑需要被Agent捕获。
- 重新运行Agent时,要模拟Lambda的事件触发:比如编写一个测试类,调用你的Lambda handler方法,或者使用AWS Lambda本地测试工具(如
sam local invoke)来触发应用,确保所有JNI相关的初始化逻辑都被Agent记录到元数据中。
2. 调整原生镜像构建参数
- 在
native-maven-plugin的buildArgs中添加-H:+JNI参数,明确开启JNI支持(虽然默认可能开启,但明确指定可以避免潜在的配置问题)。 - 检查
JNIConfigurationFiles指向的jni-config.json,确保其中包含com.amazonaws.services.lambda.runtime.api.client包下的JNI相关配置,比如JniHelper、NativeClient类的字段和方法。如果缺失,可以手动添加对应的条目,或者重新运行Agent生成完整的配置。
3. 检查Spring Boot与Lambda的原生支持配置
Spring Boot 3.x提供了对AWS Lambda的原生支持,建议使用spring-boot-starter-aws-lambda依赖,这样Spring Boot会自动处理Lambda的入口类和原生镜像配置,避免手动指定AWSLambda作为main类可能带来的配置遗漏。
- 添加依赖后,Spring Boot会生成适配Lambda的AOT代码,确保所有必要的元数据被正确包含。
4. 暂时关闭预览特性
你在Spring Boot插件中配置了--enable-preview,预览特性可能会与GraalVM的原生镜像构建存在兼容性问题。尝试移除jvmArguments和compilerArguments中的--enable-preview配置,重新构建镜像看是否能解决问题。
5. 验证元数据文件的完整性
检查META-INF/native-image下的所有配置文件,确保reachability-metadata.json包含Lambda运行时相关的类路径,resource-config.json包含必要的资源文件,dynamic-proxy-config.json包含需要动态代理的接口。如果有缺失,可以参考GraalVM的文档手动补充。
备注:内容来源于stack exchange,提问作者avalon

