GraalVM + SpringBoot 初探

GraalVM 简介

GraalVM compiles your Java applications ahead of time into standalone binaries. These binaries are smaller, start up to 100x faster, provide peak performance with no warmup, and use less memory and CPU than applications running on a Java Virtual Machine (JVM).

GraalVM reduces the attack surface of your application. It excludes unused classes, methods, and fields from the application binary. It restricts reflection and other dynamic Java language features to build time only. It does not load any unknown code at run time.

Popular microservices frameworks such as Spring Boot, Micronaut, Helidon, and Quarkus, and cloud platforms such as Oracle Cloud Infrastructure, Amazon Web Services, Google Cloud Platform, and Microsoft Azure all support GraalVM.

With profile-guided optimization and the G1 (Garbage-First) garbage collector, you can get lower latency and on-par or better peak performance and throughput compared to applications running on a Java Virtual Machine (JVM).

You can use the GraalVM JDK just like any other Java Development Kit in your IDE.

以上是来自官网上的介绍,简单来说主要包括几点:

  1. GraalVM 支持 AOT(Ahead-Of-Time),提前将 Java 应用编译成可执行的二进制文件,相较于编译成字节码由 JVM 即使编译,拥有更快的速度以及更小的内存。
  2. GraalVM 限制了 Java 的一些动态能力,例如反射、动态代理、java agent等,只允许在构建 Native Image 时使用。(AOT 模式)
  3. 优化 JVM 即时编译(JIT),使用 GraalVM 编译器替换 C2 编译器,提升运行时性能。

安装 GraalVM

 官网提供了多种下载方式,目前官网只能下载 Java17-22 的版本,如果想要下载其他版本可以到 github 下载。

由于本人使用的是 macos,所以就简单介绍下 macos 下的安装方式,流程还是很简单的,其他操作系统可以进行参考官方文档。

  1. 下载对应的 graalvm 版本
  2. 解压之后移动到 /Library/Java/JavaVirtualMachines 目录下
     

    sh

    代码解读

    复制代码

    tar -xzf graalvm-jdk-<version>_macos-<architecture>.tar.gz sudo mv graalvm-jdk-<version>_macos-<architecture> /Library/Java/JavaVirtualMachines
  3. 添加环境变量
  4. 如果是从 github 下载的低版本还需要进行一下两个步骤:
     

    sh

    代码解读

    复制代码

    # If you are using macOS Catalina and later you may need to remove the quarantine attribute from the bits before you can use them. sudo xattr -r -d com.apple.quarantine path/to/graalvm/folder/ # 官网下载的是默认自带的 gu install native-image

Java Demo

以下是官网提供的和一个纯 Java 应用 Demo:

 

java

代码解读

复制代码

package com.ylb.explore.graalvm.java; public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, Native World!"); } }

将 Java 代码编译成字节码

 

sh

代码解读

复制代码

javac HelloWorld.java

将字节码编译成可执行二进制文件

 

sh

代码解读

复制代码

$JAVA_HOME/bin/native-image com.ylb.explore.graalvm.java.HelloWorld HelloWorld

执行效果

 

sh

代码解读

复制代码

$ ./HelloWorld Hello, Native World!

总体还是非常简单的,编译时注意一下类路径即可。

SpringBoot Demo

接下来看一下一个简单的 SpringBoot

这边整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处即可】免费获取

java

代码解读

复制代码

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { long startTime = System.currentTimeMillis(); SpringApplication.run(DemoApplication.class, args); long endTime = System.currentTimeMillis(); System.out.println("cost: " + (endTime - startTime)); } } @RestController public class IndexController { @RequestMapping("/hello") public String showHelloWorld() { return """ hello world template """; } }

以上代码提供一个简单的 http 接口,接下来配置下 maven 相关插件,如果是使用 gradle 的话可以参考官方文档

 

xml

代码解读

复制代码

<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.3</version> </parent> <artifactId>windfall-explore-graalvm-spring-boot</artifactId> <properties> <java.version>21</java.version> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.main-class>com.ylb.explore.graalvm.spring.boot.DemoApplication</project.main-class> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <configuration> <!-- imageName用于设置生成的二进制文件名称 --> <imageName>${project.artifactId}</imageName> <mainClass>${project.main-class}</mainClass> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>21</source> <target>21</target> <compilerArgs>--enable-preview</compilerArgs> </configuration> </plugin> </plugins> </build> </project>

直接下执行 maven 插件将代码编译成二进制文件:

 

sh

代码解读

复制代码

mvn native:compile

这个时候遇到了编译时的第一个坑 

 这里真正的提示错误没有高亮,反而是下面的 native-image -cp 语句进行了高亮,异步流程就会错过,这里耽搁了我不少时间🌚,解决方案也很简单,在插件中添加配置参数即可

 

xml

代码解读

复制代码

<mainClass>${project.main-class}</mainClass>

添加上 mainClass 后,重新执行编译命令,一切都十分顺利 

 经过漫长的等待,终于编译成功,那么不出意外,意外马上就来了

 

sh

代码解读

复制代码

$ windfall-explore-graalvm-spring-boot . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.3.3) Application run failed org.springframework.boot.AotInitializerNotFoundException: Startup with AOT mode enabled failed: AOT initializer com.ylb.explore.graalvm.spring.boot.DemoApplication__ApplicationContextInitializer could not be found at org.springframework.boot.SpringApplication.addAotGeneratedInitializerIfNecessary(SpringApplication.java:443) at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:400) at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) at com.ylb.explore.graalvm.spring.boot.DemoApplication.main(DemoApplication.java:11) at java.base@21.0.4/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)

出现这个错误的主要原因是缺少了 spring boot 的 AOT 元文件信息,需要先通过以下命令编译生成 AOT 元文件

 

sh

代码解读

复制代码

mvn clean compil mvn spring-boot:process-aot mvn spring-boot:process-aot

接下来再执行 mvn native:compile,坐等二进制文件编译完成即可。

下面对比下使用 jvm 启动和使用二进制文件的耗时: 

 

 虽然没有到达了官网所说的 100 倍那么多,但也这个差距相当恐怖了。

以上是在本地编译成可执行文件的方式,spring 官方还提供了直接编译构建 docker 镜像的方式:

 

java

代码解读

复制代码

mvn spring-boot:build-image -Pnative

 

 内部是基于 paketobuildpacks/builder-jammy-tiny 和 paketobuildpacks/run-jammy-tiny 构建镜像,底层原理还未了解,但大概率也是作为基础镜像安装相关环境后,编译代码,然后瘦身镜像。这里有一个,请确保 docker 配置了足够多的内存,别问我怎么知道🌚

这里还有一个比较坑的点,使用 GraalVM 编译好的二进制文件,无法直接使用 JDK 提供的一下工具,例如 jstack、jstat 等等,可能对之前习惯使用这些工具的童鞋来说,是难以接受的。

 

sh

代码解读

复制代码

$ jps 43203 Main 18475 Launcher 18847 Jps $ ps -ef | grep windfall 501 18834 27099 0 10:21AM ttys003 0:00.09 /windfall-common/windfall-explore/windfall-explore-graalvm/windfall-explore-graalvm-spring-boot/target/windfall-explore-graalvm-spring-boot # 甚至会导致进程直接中断,真恐怖 $ jstack 19194 19194: No such process

总结

  • GraalVM 提供了 AOT 编译器,大大提升了代码启动效率,但同时也大大增加了编译时间。
  • GraalVM 限制了 Java 运行时动态加载的特性,可能很多深度使用 Java 动态加载的应用或者工具并不适用。
  • GraalVM AOT 编译出来的二进制文件无法使用 JDK 提供的一些监控诊断工具。
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值