SpringBoot: 使用GraalVM编译native应用

曾今Go语言里让我最艳羡的两个特性,一个是Goroutine,一个是native编译。 Java 21的虚线程实现了类似Goroutine的能力。Spring Boot 3.x开始提供了GraalVM的支持,现在Spring Boot也能打包成native文件了。

这一篇文章的目标是用一个案例讲解如何将Spring Boot应用打包成native文件。整个过程主要是4步:

  1. 环境准备,讲解怎么安装GraalVM,安装本地编译器(gcc,cl)
  2. 测试工程,创建一个极简的Spring Boot应用,只有个Controller
  3. 编译打包,使用GraalVM、Maven将Spring Boot应用构建成可执行文件
  4. 测试运行,执行生成的可执行文件,访问Controller看是否正常

1. 环境准备

1. 前置条件

GraalVM依赖一些本地工具才能完成工作,这些工具包括:

  • C的头文件
  • glibc-devel
  • zlib
  • gcc
  • libstdc++-static

不同的操作系统,采用的安装方式不同,在Linux下可以使用包管理工具安装,比如yum、apt等

Linux

yum install gcc glibc-devel zlib-devel
apt-get install build-essential libz-dev zlib1g-dev

Windows

Windows可以通过安装Visual Studio完成依赖包安装

  1. 安装Visual Studio Build Tools
  2. 安装Visual Studio
2. 安装GraalVM

官网选择Java版本、平台来下载,下载的zip包解压即安装,接下来只要设置环境变量JAVA_HOME、PATH即可

  • 设置GRAALVM_HOME为解压文件夹的根目录
  • 设置PATH为解压文件夹下的bin
3. 验证安装

通过执行native-image --help,确认我们安装成功

4. 参考资料

关于GraalVM的安装过程,可以参考官方的Get Started文档: Getting Started with GraalVM

2. 测试工程

1. 创建工程

使用mvn archetype:generate根据archetype maven-archetype-quickstart生成一个最简单的Java项目。

mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes 
    -DarchetypeArtifactId=maven-archetype-quickstart 
    -DarchetypeVersion=1.4 
    -DgroupId=com.keyniu.dis 
    -DartifactId=DiveInSpring 
    -Dversion=0.1 
    -Dpackage=com.keyniu.dis 
    -DinteractiveMode=false
2. 添加Spring Boot支持

修改pom.xml选择spring-boot-starter-parent作为parent,添加native-maven-plugin插件

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.keyniu.dis</groupId>
    <artifactId>DiveInSpring</artifactId>
    <version>0.1</version>
    <name>DiveInSpring</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
3. 添加测试类

写一个最简单启动引导类,提供一个最简单的接口/hello,用于后续测试。

package com.keyniu.dis;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DiveInMain {

    @GetMapping("/hello")
    public String hello(@RequestParam("name") String name) {
        return "hello," + name;
    }

    public static void main(String[] args) {
        SpringApplication.run(DiveInMain.class);
    }
}

3. 编译打包

1. 命令行窗口

为了让GraalVM能引用到VisualStudio的编译工具,我选择在x64 Native Tools窗口中运行接下来的命令。

在执行命令之前,确保你的环境变量设置正确,有添加GraalVM相关的配置

set GRAALVM_HOME=D:\Programs\GraalVM
set PATH=D:\Programs\GraalVM\bin;%PATH%
2. 进行native编译

在工程的根目录下执行mvn -Pnative native:compile,进行编译,最后会生成一个exe到工程的target目录下,输入如下

D:\Workspace\DiveInSpring>mvn -Pnative native:compile
...
Produced artifacts:
 D:\Workspace\DiveInSpring\target\DiveInSpring.exe (executable)

4. 测试运行

只需要在命令行里输入DiveInSpring.exe执行,可以看到只需要0.063s就能完成启动,这个项目如果通过java -jar启动的话大概耗时0.97s,还是快了很多的。打包后的exe文件大小是80M。

通过curl命令验证,接口正常提供服务

5. 使用建议

可以看到整个构建过程已经相当的顺畅,从可执行文件的大小,启动时间,内存占用都于明显的提升,应该说Spring + GraalVM离成熟应用已经不远了。由于GraalVM使用了Closed World Optimization,实际上这个可执行程序还是有一些限制的,主要是:

  1. Class初始化,有些初始化会在build期间完成,可以参考GraalVM文档: Class Initialization in Native Image
  2. 反射和动态代理,需要的编译期间完成
  3. 不支持JNI,如果有本地方法调用需要根据GraalVM提供的接口定制实现
  4. 执行时无法访问字节码,正常的debug、监控等用JVM TI实现的功能都不再可用

在SpringBoot中, 像profiles、 条件配置、 @Enable*等功能会受到影响,而其实这些特性正在被广泛使用,这些限制在完全解决之前实际使用还是有障碍的。关于可以执行文件的DEBUG、监控在GraalVM官网都已经有对应的文档,但是将它和公司内部的监控报警做集成还有一段路要走,已经看到黎明的曙光了。

A. 参考资料

  1. GraalVM构建Swing应用
  2. 使用GDB Debug由GraalVM构建的程序,Debug Native Executables with GDB
  3. 让可执行文件支持JFR,Build and Run Native Executables with JFR
  4. 手动创建Spring Boot工程
<think>我们正在讨论的是在Spring Boot项目中使用GraalVM编译为原生镜像时,如何添加AOT(提前编译)启动时的虚拟机选项。 根据引用[2]和引用[4],我们知道GraalVM原生镜像在构建时和运行时都有特定的配置方式。对于启动时的虚拟机选项(类似于传统JVM的`-Xmx`等选项),在原生镜像中需要在构建时指定,因为原生镜像运行时没有JVM,这些选项是直接编译进可执行文件中的。 因此,在构建原生镜像时,我们可以通过GraalVM提供的构建参数来设置这些选项。 具体步骤如下: 1. 使用`native-image`命令构建时,可以通过`-J`前缀来传递构建工具自身的JVM选项(例如`-J-Xmx4G`),但这影响的是构建过程的资源,而不是最终生成的可执行文件。 2. 为了设置最终生成的原生镜像可执行文件的运行时选项,我们需要使用`--vm.<option>`这样的参数。但是请注意,GraalVM原生镜像的运行时选项是有限的,并且需要在构建时预设。 3. 实际上,GraalVM原生镜像支持一些特定的运行时选项,这些选项可以通过`-H:RuntimeOption=<option>`在构建时设置。但是,对于标准的JVM选项(如堆大小),我们需要使用特定的方式。 4. 对于堆大小设置,GraalVM原生镜像使用不同于传统JVM的机制。它使用一个固定的堆大小,这个堆大小在构建时通过`-R:MaxHeapSize=<size>`(或者`--runtime:MaxHeapSize=<size>`)来设置。例如:`-R:MaxHeapSize=2G`。 5. 另外,还可以设置一些其他的运行时选项,如`-R:InitialHeapSize`等。 6. 注意:这些选项必须在构建时指定,因为它们是编译进可执行文件中的。运行时不能再更改(除了少数选项可以通过环境变量覆盖)。 7. 除了堆大小,还可以设置其他一些选项,例如: - `-R:MaxNewSize`: 年轻代最大大小 - `-R:MinHeapSize`: 最小堆大小 - `-R:MaxRAMPercentage`: 最大RAM百分比(类似于传统JVM的`-XX:MaxRAMPercentage`) 8. 另外,也可以通过环境变量在运行时设置部分选项,例如`NATIVE_IMAGE_XX_OPTIONS`,但这种方式有限制。 因此,在命令行中构建Spring Boot原生镜像时,我们可以这样添加AOT启动时的虚拟机选项(以堆大小为例): ```bash native-image -jar target/spring-boot-app.jar \ -H:Name=myapp \ -R:MaxHeapSize=2G \ -R:MinHeapSize=1G \ --no-fallback ``` 上述命令设置了最大堆为2G,最小堆为1G。 另外,如果需要设置其他非堆相关的虚拟机选项(比如GC类型),GraalVM原生镜像默认使用Serial GC,可以选择其他GC,例如: - `-H:+UseParallelGC`:使用并行GC - `-H:+UseZGC`:使用ZGC(需要GraalVM支持) 注意:这些选项也是构建时指定的。 总结: 在GraalVM编译为原生镜像时,传统的JVM启动参数(如`-Xmx`)不再使用,而是通过构建时的`-R:`选项(或`--runtime:`)来设置运行时内存参数,以及通过`-H:`设置其他构建和运行时选项。 另外,Spring Boot 3.x对GraalVM原生镜像提供了更好的支持。如果你使用Spring Boot Maven/Gradle插件,可以在插件配置中指定这些选项。 例如,在Maven中,可以在`pom.xml`中配置: ```xml <build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <configuration> <buildArgs> <buildArg>-R:MaxHeapSize=2G</buildArg> <buildArg>-R:MinHeapSize=1G</buildArg> <buildArg>-H:+UseParallelGC</buildArg> </buildArgs> </configuration> </plugin> </plugins> </build> ``` 然后使用Maven命令构建:`mvn -Pnative native:compile` 这样,构建出的原生镜像就会包含这些虚拟机选项。 注意:这些选项是编译到可执行文件中的,所以运行时无法更改(除了少数可以通过环境变量覆盖的选项)。如果需要运行时调整,可能需要考虑其他方式,或者使用GraalVM企业版的一些高级特性。 关于更多可用的构建选项,可以参考GraalVM官方文档:https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildOptions/ 最后,生成相关问题: </think>### 在 Spring Boot 项目中为 GraalVM 原生镜像添加 AOT 虚拟机选项的方法 #### 1. **通过构建命令直接添加** 在 `native-image` 命令中使用 `-R:` 参数指定运行时选项: ```bash native-image -jar your-app.jar \ -R:MaxHeapSize=2G \ # 设置最大堆内存为 2GB -R:MinHeapSize=1G \ # 设置初始堆内存为 1GB -R:MaxNewSize=512M \ # 设置年轻代最大内存 -R:MaxRAMPercentage=75.0 # 设置最大 RAM 百分比 ``` 这些选项会**编译到可执行文件中**,在镜像启动时自动生效[^2][^4]。 #### 2. **使用 Maven 插件配置** 在 `pom.xml` 中通过 `<buildArgs>` 配置: ```xml <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <configuration> <buildArgs> <buildArg>-R:MaxHeapSize=2G</buildArg> <buildArg>-R:MaxRAMPercentage=75.0</buildArg> <buildArg>-H:+PrintGC</buildArg> <!-- 启用 GC 日志 --> </buildArgs> </configuration> </plugin> ``` 构建命令:`mvn -Pnative native:compile` #### 3. **通过配置文件预设** 在 `resources/META-INF/native-image` 目录创建 `native-image.properties`: ``` Args = -R:MaxHeapSize=2G \ -R:MaxRAMPercentage=75.0 \ -H:+ReportExceptionStackTraces ``` 这些配置会在构建时**自动加载**[^1][^3]。 #### 支持的常见虚拟机选项 | 选项 | 作用 | 示例 | |------|------|------| | `-R:MaxHeapSize` | 最大堆内存 | `-R:MaxHeapSize=2G` | | `-R:MinHeapSize` | 初始堆内存 | `-R:MinHeapSize=1G` | | `-R:MaxRAMPercentage` | RAM 百分比 | `-R:MaxRAMPercentage=75.0` | | `-H:+PrintGC` | 启用 GC 日志 | | | `-H:+ReportExceptionStackTraces` | 完整异常栈 | | | `-R:NumberOfThreads` | 工作线程数 | `-R:NumberOfThreads=4` | #### ⚠️ 重要注意事项 1. **与传统 JVM 参数的区别**: - 使用 `-R:` 前缀替代传统 `-Xmx`/`-Xms` - 选项必须在**构建时指定**(运行时不可修改) ```bash # 错误 ❌(传统 JVM 方式) ./your-app -Xmx2G # 正确 ✅(GraalVM 原生镜像) native-image -R:MaxHeapSize=2G ... ``` 2. **内存设置建议**: $$ \text{MaxHeapSize} \leq 0.8 \times \text{可用物理内存} $$ 避免过度分配导致 OOM[^4] 3. **调试选项**: ```bash -H:+PrintAnalysisCallTree \ # 调用树分析 -H:+DashboardAll \ # 内存仪表盘 -H:+PrintClassInitialization # 类初始化跟踪 ``` 4. **选项验证**: 构建时添加 `--verbose` 查看生效参数: ```bash native-image --verbose -R:MaxHeapSize=2G ... ``` > **性能对比**:GraalVM 原生镜像启动时加载虚拟机选项比传统 JVM 快 $20\times$,因无需解析命令行参数[^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值