GraalVM 是 Oracle 主导的高性能 JDK 发行版,核心由三部分组成:Graal 编译器(用 Java 编写的 JIT 编译器,可替代 HotSpot 的 C2)、Native Image(AOT 提前编译,将 Java 程序编译为本地可执行文件)、Truffle 多语言框架(支持在同一进程中运行 JavaScript、Python 等语言)。与普通 OpenJDK 相比,GraalVM 的主要价值体现在两点:作为 JIT 编译器带来更高的峰值性能,以及通过 Native Image 实现毫秒级启动和极低的内存占用。

安装 GraalVM

推荐使用 SDKMAN! 管理多个 JDK 版本:

1
2
3
4
5
6
7
8
# 安装 GraalVM (以 25 版本为例)
sdk install java 25-graalce

# 切换当前会话使用
sdk use java 25-graalce

# 验证安装
java -version

输出示例:

1
2
3
openjdk version "25" 2025-09-16
OpenJDK Runtime Environment GraalVM CE 25+37.1
OpenJDK 64-Bit Server VM GraalVM CE 25+37.1

也可以从 GraalVM 官网或 GitHub Releases 直接下载压缩包,解压后设置 JAVA_HOME 指向解压目录即可。

两种运行模式

JIT 模式:作为普通 JDK 使用

GraalVM 本身就是完整的 JDK,可以直接编译运行 Java 程序。默认情况下,JVM 使用 Graal 编译器替代 C2 作为顶层 JIT 编译器,在部分负载下(尤其是大量使用 Stream、Lambda、装箱操作的代码)峰值性能优于 C2。

1
2
javac Hello.java
java Hello

如需对比,可以使用 -XX:-UseJVMCICompiler 回退到 C2 编译器。

Native Image:AOT 编译为本地可执行文件

Native Image 是 GraalVM 的核心卖点。它在构建期执行封闭世界假设(closed-world assumption)分析:从入口方法出发做可达性分析,只把可达的类、方法和资源编译进一个独立的本地可执行文件,运行时不再需要 JVM。

主要收益:

  • 启动极快:通常在几十毫秒内启动,适合 Serverless、CLI 工具、短生命周期任务。
  • 内存占用低:无需加载 JVM 和 JIT 编译,常驻内存可降至传统 JVM 的 1/5 甚至更低。
  • 独立分发:产物不依赖 JDK,镜像体积极小(配合静态链接可跑在 scratch 镜像中)。

代价:

  • 构建耗时长:可达性分析 + AOT 编译通常需要数分钟。
  • 吞吐量可能略低:缺少 JIT 的运行期画像优化(PGO 可缓解)。
  • 动态特性受限:反射、动态代理、资源加载、JNI 等需要显式配置。

Native Image 快速上手

以最简单的程序为例:

1
2
3
4
5
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, GraalVM!");
}
}

编译并生成可执行文件:

1
2
3
4
5
6
7
8
# 编译为字节码
javac Hello.java

# AOT 编译为本地可执行文件
native-image Hello

# 运行
./hello

常用构建参数:

1
2
3
4
5
6
7
8
native-image \
-cp build/classes \ # 类路径
-H:Name=myapp \ # 输出文件名
-H:Class=com.example.Main \ # 主类
-O2 \ # 优化级别(O0-O3)
--no-fallback \ # 失败时直接报错,不回退 JVM
--static \ # 静态链接(需 musl)
com.example.Main

--no-fallback 在生产构建中必须加上:默认情况下构建失败会生成一个回退到 JVM 运行的镜像,掩盖问题。

反射与动态特性配置

Native Image 的封闭世界假设意味着所有反射访问必须在构建期声明。以下代码在 JVM 上正常,但直接编译成 native image 会在运行期报错:

1
2
3
4
5
6
7
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("com.example.Service");
Object obj = cls.getDeclaredConstructor().newInstance();
System.out.println(obj.getClass().getName());
}
}

解决方式有两种:

使用 Tracing Agent 自动生成配置

在 JVM 模式下挂载 agent 运行应用,自动记录所有反射、资源、代理、序列化的使用,生成配置文件:

1
2
3
java -agentlib:native-image-agent=\
config-output-dir=META-INF/native-image \
-jar myapp.jar

生成的 META-INF/native-image/ 目录下包含:

  • reflect-config.json:反射配置
  • resource-config.json:资源文件配置
  • proxy-config.json:动态代理配置
  • serialization-config.json:序列化配置

这些文件放入类路径后,native-image 会自动读取。

手工编写配置

对于无法完整运行应用的场景,可手工编写 reflect-config.json

1
2
3
4
5
6
7
[
{
"name": "com.example.Service",
"allDeclaredConstructors": true,
"allDeclaredMethods": true
}
]

构建时通过 -H:ReflectionConfigurationFiles=reflect-config.json 指定。

使用 Maven 插件构建

GraalVM 官方提供 native-maven-plugin,将 native 编译集成到构建流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.11.0</version>
<executions>
<execution>
<id>build-native</id>
<goals><goal>compile-no-fork</goal></goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<buildArgs>
<buildArg>--no-fallback</buildArg>
<buildArg>-O2</buildArg>
</buildArgs>
</configuration>
</plugin>

执行构建:

1
mvn package -Pnative

产物输出在 target/ 目录下,可直接执行。

Spring Boot 4 集成

Spring Boot 4.0(2025 年 11 月发布,基于 Spring Framework 7、Jakarta EE 11)原生支持 GraalVM,通过 Spring AOT 引擎在构建期处理 Bean 定义、条件注解和配置绑定,解决了框架大量反射使用与封闭世界假设的冲突。Spring 官方维护的 Reachability Metadata 仓库(graalvm-reachability-metadata)覆盖了绝大多数主流第三方库。

与 Spring Boot 3 相比,4.x 在 native 方向的关键变化是工具链基线提升,而非编程模型变化:

  • Native Image 构建要求 GraalVM 25+(JVM 模式运行仍只需 Java 17+),低于该基线的镜像启动时会被拒绝。
  • Native Build Tools 升级到 0.11,构建命令保持不变。
  • spring-jcl 已移除,改用普通 commons-logging;Log4j2 的 native 支持自 4.0.1 起可用,Logback 保持支持。
  • 因 Servlet 6.1 基线要求,Undertow 支持被移除,相关应用需迁移到 Tomcat/Jetty。

构建 native 版本 Spring Boot 应用:

1
2
3
4
5
# 本地编译
./mvnw native:compile -Pnative

# 或在 Docker 中编译,无需本地安装 GraalVM
./mvnw spring-boot:build-image -Pnative

辅助工作流(4.x 官方推荐):

1
2
3
4
5
# 在 JVM 上验证 AOT 生成的代码路径,无需等待 native 编译
java -Dspring.aot.enabled=true -jar target/myapp.jar

# 运行 native 测试:构建 native 测试镜像并在其中执行测试
./mvnw -PnativeTest test

注意 Spring AOT 的构建期固化约束:类路径在构建期固定,@ConditionalOnProperty 等条件注解在 AOT 处理时已求值,端口、日志级别等普通配置仍可在运行期调整,但 Bean 图结构不能靠部署期配置改变。

此外,Spring Boot 4 文档新增了与 GraalVM 无关的 Java 25 AOT Cache 方案:通过训练缓存加速 JVM 启动(官方建议 Java 25+ 时优先于 CDS),适合想要更快启动但又不想承担 native 兼容性约束的场景。

实测效果(典型 REST 应用):

指标JVM 模式Native Image
启动时间2~4 秒0.05~0.1 秒
常驻内存200~400 MB50~100 MB
镜像大小200 MB+50~80 MB
峰值吞吐略低(可用 PGO 弥补)

适用场景

Native Image 并非银弹,选型时按场景判断:

适合:

  • Serverless / FaaS 函数:冷启动时间直接决定成本和延迟。
  • CLI 命令行工具:用户无法接受秒级启动等待。
  • 容器化微服务:低内存占用意味着更高的部署密度。
  • 短生命周期批处理任务:JVM 预热成本占比过高。

不适合:

  • 长运行、吞吐量敏感的服务:JIT 的运行期优化仍有优势。
  • 重度依赖反射、动态类加载的框架或库:配置成本可能很高。
  • 需要 JVM 诊断工具链(JFR、Arthas 等)深度排障的系统。

多语言支持

GraalVM 的 Truffle 框架允许在 JVM 上运行其他语言,并实现跨语言互操作:

1
2
3
4
5
6
7
8
9
10
import org.graalvm.polyglot.*;

public class PolyglotDemo {
public static void main(String[] args) {
try (Context ctx = Context.create()) {
Value result = ctx.eval("js", "1 + 2");
System.out.println(result.asInt()); // 输出 3
}
}
}

该特性适用于规则引擎、脚本扩展、插件系统等需要嵌入脚本的场景,纯 Java 应用一般用不到。

总结

GraalVM 提供了两条使用路径:作为 JDK 替换获得更好的 JIT 性能,以及通过 Native Image 获得极致的启动速度和内存效率。实践中 Spring Boot 4 + Native Image 已成为云原生 Java 的主流方案之一,但引入前应评估框架兼容性、构建成本和诊断工具的缺失;从 Boot 3 升级时需将 native 构建工具链提升到 GraalVM 25+ 并重建全部 AOT 元数据。对于新项目,建议从第一天起就用 --no-fallback 构建并接入 CI,尽早暴露反射配置问题。