SpringBoot3.0 +GraalVM17 + Docker
GraalVM 技术解析:背景、与JDK的区别及核心特点
技术背景与发展历程
GraalVM 是由 Oracle 开发的高性能通用虚拟机平台,其发展脉络如下:
-
起源(2010s初期):
-
诞生于 Oracle Labs 研究项目,旨在解决传统 JVM 的局限性
-
核心目标:打破语言边界,优化云原生场景性能
技术里程碑:
-
2018年:首个生产版本发布
-
2019年:成为 OpenJDK 项目(GraalVM Community Edition)
-
2022年:Spring Boot 3 原生支持成为行业引爆点
设计哲学:
-
多语言统一运行时(“One VM to Rule Them All”)
-
提前编译(AOT)与即时编译(JIT)协同优化
-
云原生优先架构
与标准JDK的本质区别
维度 | 传统JDK (OpenJDK) | GraalVM |
---|---|---|
核心架构 | 单语言运行时(JVM) | 多语言运行时(Polyglot VM) |
编译方式 | 纯JIT编译(运行时优化) | JIT + AOT混合模式 |
启动性能 | 较慢(需JIT预热) | 极快(原生镜像毫秒级启动) |
内存占用 | 较高(需加载完整JVM) | 极低(SubstrateVM仅MB级) |
语言支持 | 仅JVM系语言 | Java + JS/Python/Ruby/R/LLVM |
动态能力 | 完整反射/JNI支持 | 受限(需编译期配置) |
部署形态 | JAR包依赖JRE | 独立可执行文件 |
GraalVM 的 AOT(Ahead-of-Time)编译 是其颠覆性创新之一,通过 Native Image 技术实现
AOT(Ahead-Of-Time)编译是相对于 JIT(Just-In-Time)的编译模式:
- JIT 编译:Java 传统模式,运行时动态编译热点代码,启动时需要类加载和即时编译过程
- AOT 编译:构建阶段提前将字节码编译为平台原生机器码,生成独立可执行文件(如 Native Image)
GraalVM的限制
-
GraalVM在编译成二进制可执行文件时,需要确定该应用到底用到了哪些类、哪些方法、哪些属性,从而把这些代码编译为机器指令(也就是exe文件)。
-
但是我们一个应用中某些类可能是动态生成的,也就是应用运行后才生成的,为了解决这个问题,GraalVM提供了配置的方式,比如我们可以在编译时告诉GraalVM哪些方法会被反射调用,比如我们可以通过reflect-config.json来进行配置。
环境准备
-
GraalVM下载地址
目前版本只有17 +
-
配置对应的JAVA_HOME和PATH环境变量
mac为例:
vim ~/.bash_profile 写入下面命令后,执行source ~/.bash_profile 以生效# graalvm-jdk-17 这个主要可以生成二进制文件 JAVA_HOME=/Library/Java/JavaVirtualMachines/graalvm-jdk-17.0.14+8.1/Contents/Home PATH=$JAVA_HOME/bin:$PATH:. export JAVA_HOME export PATH
安装完毕之后可以通过java -version来进行验证:
-
添加Native Image支持
我们安装GraalVM的目的就是使用它的native Image特性。native image是一个单独的jar包,我们可以执行下面的命令来进行安装:gu install native-image
其中gu就是/Library/Java/JavaVirtualMachines/graalvm-jdk-17.0.14+8.1/Contents/Home/bin中的命令。
构建spring boot3应用
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.7</version><relativePath/></parent><groupId>org.example</groupId><artifactId>graalvm-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>graalvm-demo</name><description>graalvm-demo</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
步骤一:命令行启动、验证项目是否能以 native 方式启动
# 启动命令
mvn -Pnative spring-boot:run
步骤二:命令行编译二进制文件
# 打包命令
mvn -Pnative native:compile
- 编译打包比较耗时
- 编译打包后会在target下生成二进制文件以及jar包
- 二进制启动命令
cd target
./graalvm-demo
- 启动速度为 0.066 秒
- 运行内存占用为21M
#查看进程
lsof -i :8080
# 查看内存
top -l 1 -pid 88941 -stats pid,ppid,user,mem,cpu,vsize,command
- jar启动
cd target
java -jar demo.jar
- 启动速度为 1.689秒
- 运行内存占用 125M
#查看进程
lsof -i :8080
# 查看内存
top -l 1 -pid 88941 -stats pid,ppid,user,mem,cpu,vsize,command
若要以idea进行打包编译
*方式一: 根据图片中的顺序执行即可得到可执行的二进制文件。
- 方式二:把上面图片中的流程配置到 pom.xml 中, 执行流程变成下图所示
<build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId><configuration><imageName>${project.artifactId}</imageName></configuration><executions><execution><id>build-native</id><goals><goal>compile-no-fork</goal></goals><phase>package</phase></execution></executions></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><mainClass>org.example.graalvmdemo.GraalvmDemoApplication</mainClass></configuration><executions><!-- 添加 AOT 预处理步骤 --><execution><id>process-aot</id><phase>prepare-package</phase><goals><goal>process-aot</goal></goals></execution></executions></plugin></plugins></build>
不需要在手动执行 spring-boot:process-aot 与 native:compile
编写dockerfile来进行CI/CD
云原生时代,我们直接利用docker进行跨平台操作。
明确编译期间的镜像
我们希望将java程序打包成可执行文件可以利用官网提供的docker镜像
官网graalvm环境镜像的地址
进入地址后要注意观察下面几个文字:
17.0.9代表是GraalVM17版本
ol9代表运行的环境是oraclelinux:9-slim
在docker上执行命令
docker pull ghcr.io/graalvm/graalvm-community:17.0.9-ol9
我们下载到这个包含了native-image的运行底层库和运行环境和jdk17环境的镜像后,还需要做一件事,因为我们程序是基于maven进行构建的,所以我们需要基于该镜像构建一个带有maven的镜像,所以受累我们要先写一个构建环境的dockerfile。
进入 https://downloads.apache.org/maven/maven-3/
查看maven都有哪些版本
# 使用 GraalVM 官方镜像为基础
FROM ghcr.io/graalvm/graalvm-community:17.0.9-ol9
# 设置 Maven 的版本和相关路径
ENV MAVEN_VERSION=3.9.10
ENV MAVEN_HOME=/usr/share/maven
ENV PATH=$MAVEN_HOME/bin:$PATH
# 安装必要的依赖(确保curl、tar、gzip已安装)# 安装 Maven
RUN curl -fsSL https://downloads.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \| tar -xz -C /usr/share && mv /usr/share/apache-maven-${MAVEN_VERSION} /usr/share/maven
# 验证 Maven 和 GraalVM 的 native-image
RUN java -version && mvn --version && native-image --version
# 设置工作目录
WORKDIR /app
# 将自定义的 settings-jdk17.xml 替换为 Maven 的用户级配置 settings.xml
COPY ./setting_jdk17.xml /root/.m2/settings.xml
# 选择默认启动命令(可选)
CMD ["bash"]
这里我们选择的maven版本是 3.9.10 ,同时里面有一句
COPY ./setting_jdk17.xml /root/.m2/settings.xml
这个目的是将我本地的settings.xml替换掉默认的xml,避免由于一些特殊原因拉不到镜像的情况。
settings-pdk.xml的文件位置一定要和Dockerfile同级,否则可能会找不到
执行如下命令构建新的镜像
docker build -t pdk-graalvm17-ol9-mvn3.9.10 .
执行 docker images 可看到刚刚构建的镜像
- 开始编写编译的dockerfile
FROM pdk-graalvm17-ol9-mvn3.9.10 AS build# 设置工作目录
WORKDIR /appCOPY . .# clean + package
RUN mvn clean compile package
RUN mvn install# 执行native-image编译
RUN mvn -Pnative native:compile -DskipTests
# 切换工作目录到target下
WORKDIR /app/target
# 指定端口是8080
EXPOSE 8080
CMD ["/app/target/graalvm-demo"]
- 控制台执行
docker buildx build --platform linux/amd64 -t graalvm-demo:latest .
观察镜像会发现一个离谱的事, 镜像太大了,达到了1.65G
因为我们把GraalVM和maven等工具链都打包到里面了,所以为了瘦身,我们只需要保留基础的oraclelinux:9-slim即可。
# ==========================================================
# 第一阶段:基于现有基础镜像完成 Maven 构建和 GraalVM 编译 (Build Stage)
# ==========================================================
FROM pdk-graalvm17-ol9-mvn3.9.10 AS build# 设置工作目录
WORKDIR /appCOPY . .RUN mvn -Pnative native:compile# ==========================================================
# 第二阶段:精简运行环境 (Runtime Stage)
# ==========================================================
FROM oraclelinux:9-slim AS runtime# 设置工作目录
WORKDIR /app# 将构建阶段的原生可执行文件复制到运行镜像
COPY --from=build /app/target/graalvm-demo /app/graalvm-demo# 确保可执行文件有运行权限
RUN chmod +x /app/graalvm-demo# 暴露服务使用的端口
EXPOSE 8080# 设置启动命令(运行原生二进制文件)
CMD ["/app/graalvm-demo"]
执行完毕后发现镜像大小变小了
查看执行日志和结果
什么是AOT
AOT(Ahead-Of-Time Compilation)即提前编译,是一种与传统 JIT(Just-In-Time Compilation)相对的编译策略。在 Java 领域,AOT 近年来因 GraalVM 等技术而备受关注。以下是对 AOT 的详细解释:
AOT 是指在程序运行前(而非运行时)将源代码或中间代码编译为机器码的过程。
传统 JVM 工作流程(JIT):
- Java 源代码编译为字节码(.class 文件)。
- JVM 加载字节码并解释执行。
- JIT 编译器在运行时将热点代码编译为机器码以提高性能。
AOT 工作流程:
- Java 源代码编译为字节码。
- AOT 编译器(如 GraalVM Native Image)将字节码直接编译为平台特定的机器码。
- 生成独立可执行文件(无需 JVM 即可运行)。
AOT 的核心优势
1.启动速度极快
- 无需 JVM 加载和类初始化过程,直接执行机器码。
- 适用于 serverless 函数、微服务等需要快速冷启动的场景。
2.内存占用更低
- 不包含 JVM 运行时环境,二进制文件体积小。
- 减少类加载器、JIT 编译器等组件的内存开销。
3. 安全增强
- 编译后的代码更难逆向工程。
- 可通过静态分析移除未使用的代码(如字符串解析、反射)。
AOT的原理
Maven 插件执行流程详解
当执行 mvn -Pnative native:compile 时,native-maven-plugin 的执行流程大致如下:
1. 编译主代码: Maven 先编译项目的 Java 源代码到 target/classes。
2. AOT 处理阶段:
- ProcessAotMojo.executeAot() 调用 Spring 的 AOT 处理器。
- SpringApplicationAotProcessor:扫描应用程序,生成优化后的配置类和代理类。
- ContextAotProcessor:分析 Spring 上下文,生成以下内容:
- spring-aot/main/sources:优化后的 Java 源代码(如 ContextBootstrapInitializer)。
- spring-aot/main/resources/META-INF/native-image:GraalVM 配置文件(如 reflect-config.json)。
3. 资源整合:
- 将生成的 Java 类编译为 class 文件并复制到 target/classes。
- 将 GraalVM 配置文件复制到 target/classes/META-INF/native-image。
4. GraalVM 编译:
- 使用 Native Image 插件读取配置文件,将应用编译为原生二进制文件。
RuntimeHints 机制的作用
Spring Framework 6 的 RuntimeHints 机制允许开发者在运行时声明性地指定反射、代理、资源加载等元数据,帮助 GraalVM Native Image 理解应用程序的动态行为,避免手动编写 JSON 配置文件。
核心接口
RuntimeHintsRegistrar:注册运行时提示的接口。
RuntimeHints:提供 API 注册反射、代理、资源等提示。
@ImportRuntimeHints 是 Spring Framework 6 引入的一个注解,用于声明式注册运行时提示(Runtime Hints),帮助 GraalVM Native Image 理解应用程序的动态行为(如反射、代理、资源访问等)。这个注解提供了一种更简洁的方式来替代传统的 RuntimeHintsRegistrar 接口实现。
- 注解作用与原理
作用:告诉 Spring 在 AOT 处理阶段收集并生成 GraalVM 需要的元数据配置(如 reflect-config.json)。
原理:Spring AOT 处理器会扫描带有 @RuntimeHintsRegistrar 的类,调用其指定的注册器方法,收集提示信息并转换为 GraalVM 配置文件。
示例:
@Service
@ImportRuntimeHints(UserServiceImpl.UserServiceRuntimeHints.class)
public class UserServiceImpl implements UserService {@Overridepublic String getUserById(Long id) {String name = "";try {Method getTestName = User.class.getMethod("getTestName");name = (String) getTestName.invoke(User.class.newInstance());} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);}return name;}@Overridepublic String getWelcomeMessage() {// 读取资源文件Properties props = new Properties();try (InputStream is = getClass().getResourceAsStream("/static/messages.properties")) {props.load(is);return props.getProperty("welcome", "Default Welcome");} catch (IOException e) {return "Error loading message";}}static class UserServiceRuntimeHints implements RuntimeHintsRegistrar {@Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {hints.resources().registerPattern("static/*");try {hints.reflection().registerConstructor(User.class.getConstructor(), ExecutableMode.INVOKE);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}}
AOT 的局限性
1. 编译时间长
- 静态分析和优化过程可能耗时较长(尤其对于大型应用)。
2. 动态特性支持受限
- 反射、动态代理、类加载等需要显式配置。
- 无法支持完全动态的代码生成(如 Class.forName() 接收用户输入)。
3. 平台依赖性
- 生成的二进制文件是平台特定的(如 Linux x86_64、macOS ARM64)。
graalvm下载地址:https://www.oracle.com/java/technologies/downloads/#graalvmjava17
github:https://github.com/graalvm
阿里文档:https://sca.aliyun.com/docs/2023/user-guide/graalvm/quick-start/?spm=7145af80.204296fe.0.0.7bf2d7feH4Rhxe
spring文档:https://docs.spring.io/spring-boot/
spring-framework文档:https://docs.spring.io/spring-framework/reference/core/aot.html
gitee测试用例:https://gitee.com/zhang-jinlong1/graalvm-demo