当前位置: 首页 > news >正文

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-aotnative: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
# 验证 MavenGraalVMnative-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 接口实现。

  1. 注解作用与原理
    作用:告诉 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

http://www.lryc.cn/news/589100.html

相关文章:

  • AI大模型训练相关函数知识补充
  • MongoDB基础增删改查命令
  • vscode配置运行完整C代码项目
  • B/S 架构通信原理详解
  • 高标准农田气象站的功能
  • 亚矩阵云手机:破解 Yandex 广告平台多账号风控难题的利器
  • 云服务器如何管理数据库(MySQL/MongoDB)?
  • 《大数据技术原理与应用》实验报告四 MapReduce初级编程实践
  • Keepalived双机热备概述
  • 死锁问题以及读写锁和自旋锁介绍【Linux操作系统】
  • Sersync和Rsync部署
  • 免杀学习篇(1)—— 工具使用
  • Dify的默认端口怎么修改
  • 算法学习day16----Python数据结构--模拟队列
  • Nuxt3宝塔PM2管理器部署
  • linux系统------LVS+KeepAlived+Nginx高可用方案
  • LVS(Linux Virtual Server)详细笔记(理论篇)
  • 李宏毅《生成式人工智能导论》 | 第9讲 AI Agent
  • Jfinal+SQLite java工具类复制mysql表数据到 *.sqlite
  • 设计模式笔记_结构型_适配器模式
  • Redis 中的持久化机制:RDB 与 AOF
  • 基于STM32设计的智能厨房
  • redis快速入门教程
  • JavaScript进阶篇——第四章 解构赋值(完全版)
  • Bash shell用法
  • 轻松管理多个Go版本:g工具安装与使用
  • 【自学linux】计算机体系结构和操作系统第二章
  • OpenCV 伽马校正函数gammaCorrection()
  • PG备份一(逻辑备份)
  • 算法与前端的可访问性