如何用 GraalVM 将 Java 应用编译成原生镜像?
介绍安装 GraalVM、验证 native-image、编译 Hello 示例及用 Maven 插件构建 Java 原生可执行文件的完整流程,并提示 JDK 21 起工具随附、构建需本机 C 工具链。
学完这篇你能得到一个不依赖 JVM、双击就能跑的原生可执行文件:从安装 GraalVM 到用 Maven 插件编译自己的项目,全程可直接照着敲。
第一步:装好 GraalVM,确认 native-image 命令能跑
这一步装好编译器并验证环境,做完后用 native-image --version 能看到版本号就算过了。
Linux/macOS 推荐用 SDKMAN 安装:
sdk install java 21.0.2-graalce
sdk use java 21.0.2-graalce
java -version
也可以直接下压缩包,解压后设置环境变量:
tar -xzf graalvm-jdk-21_linux-x64_bin.tar.gz
export JAVA_HOME=/opt/graalvm-jdk-21.0.2+13.1
export PATH=$JAVA_HOME/bin:$PATH
然后确认工具链:
native-image --version
注意:GraalVM for JDK 21 起
native-image已随 JDK 一起分发,不用再装;如果你用的是 JDK 17 时代的 GraalVM 22.x,需要额外执行gu install native-image,否则会提示 command not found。
第二步:先拿一个 Hello 程序试编译
这一步验证整条链路是通的,做完后你会得到一个几十 MB 的可执行文件,启动时间在毫秒级。
新建 Hello.java:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Native Image");
}
}
编译并生成原生镜像:
javac Hello.java
native-image Hello
./hello
native-image Hello 默认产出名为 hello 的可执行文件,--no-fallback 参数建议加上,这样缺配置时直接报错,而不是偷偷退化成需要 JVM 的模式,方便你早点发现问题。
注意:构建需要本机 C 工具链。Linux 装
gcc、zlib-devel、glibc-devel;macOS 装 Xcode Command Line Tools(xcode-select --install);Windows 装 Visual Studio 2022 生成工具加 Windows SDK。缺了会在构建中期报gcc: command not found之类的错误。
第三步:用 Maven 编译真实项目
这一步把上面的流程搬进自己的工程,做完后 mvn -Pnative package 就能产出原生可执行文件。
在 pom.xml 的 <build><plugins> 里加 GraalVM 官方的 native 插件:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.3</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals><goal>compile-no-fork</goal></goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
然后执行:
mvn -Pnative package
产物在 target/ 下,是当前平台的可执行文件。Gradle 项目加 org.graalvm.buildtools.native 插件后跑 ./gradlew nativeCompile。Spring Boot 3.x 项目只要在 Initializr 建项目时勾选 GraalVM Native Support,直接用 mvn -Pnative native:compile 即可。
注意:构建过程很吃内存,默认可能占用 4~8GB。机器内存小于 8GB 时用
-J-Xmx6g限制上限,否则容易 OOM 中断。另外native-image不支持交叉编译,Linux 上构建的产物只能在 Linux 跑,Windows 产物只能在 Windows 跑。
第四步:补齐反射、资源和动态代理配置
这一步解决「编译能过但运行时报 ClassNotFoundException / 反射拿不到方法」的问题,做完后程序行为才和 JVM 上一致。
GraalVM 在构建期做静态分析,用反射动态加载的类它看不见。最省事的办法是用 agent 跑一遍程序,把配置采集出来:
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image -jar your-app.jar
正常点一遍功能后停止程序,目录里会生成 reflect-config.json、resource-config.json、proxy-config.json 等文件,打包时会自动读取。手工维护则用 -H:ReflectionConfigurationFiles=reflect-config.json 指定。
注意:agent 只采集你实际走到的代码路径,冷门分支的反射调用采不到。上线前把关键业务路径都点一遍,或者给关键类手工补
reflect-config.json。
第五步:验证产物
这一步确认结果达标。检查三件事:文件大小(简单程序通常 10~30MB,Spring Boot 应用常见 80~150MB)、启动耗时(用 time ./hello 对比 JVM 启动)、功能是否完整。加 -H:+ReportExceptionStackTraces 构建,运行时报错时堆栈更可读。
小结
- GraalVM for JDK 21 起
native-image已内置,老版本才需要gu install - 先拿 Hello 程序跑通链路,再上 Maven 插件,排查成本最低
- 构建依赖本机 C 工具链,且不支持交叉编译
- 构建吃内存,用
-J-Xmx控制;加--no-fallback让问题早暴露 - 反射、资源、动态代理必须靠 agent 采集或手工配置
转载请注明出处,版权归原作者所有。