抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

GraalVM安装和基本使用

GraalVM是Oracle出品的,一个想要统一天下的虚拟机,最主要的是它支持AOT模式,今天就来体验一下吧。

官网

进入下载页面,选择一个合适的版本下载,这里下载的是windows平台,java17的64位ce版本。

下载完之后,解压到你想要放的目录,可以使用以下指令加入到环境变量。

# 打开cmd
setx /M PATH "D:\SDK\JDK\graalvm-ce-java17-22.0.0.2\bin;%PATH%"
setx /M JAVA_HOME "D:\SDK\JDK\graalvm-ce-java17-22.0.0.2"
# 执行完以上指令后,关闭cmd窗口后再重新打开,检查环境变量是否修改成功。
echo %PATH%
echo %JAVA_HOME%

搭建 Native Image 环境

这次主要是想体验下native,所以先搭建naive环境。

如果配置了环境的话,可以跳过这个环节,如果没有的话,可以进入目录的 bin 文件夹下执行。

gu install native-image

如果是Linux,想要编译native镜像,还需要 glibc-develzlib-develgcc

# 如果是Oracle Linux,使用yum安装
sudo yum install glibc-devel zlib-devel gcc
# 如果是 Ubuntu Linux,使用 apt-get 安装
sudo apt-get install build-essential libz-dev zlib1g-dev
# 如果是其他Linux发行版,可以使用费 dnf 包管理工具安装
sudo dnf install glibc-devel zlib-devel libstdc++-static

如果是macOS,使用 xcode

xcode-select --install

如果是 windows 的话,要安装 Visual Studio 和 微软的 Visual C++ (MSVC)。注意要安装 Visual Studio Build Tools 是 Windows 10 SDK。

而且,构建 native image 时,需要工作在 x64 Native Tools Command Prompt 环境下工作。

环境搭建好后,就可以使用 native-image 命令进行编译了。

Windows使用native-image

搜索 x64 Native Tools Command Prompt,然后打开,进入 graalVM 的 bin 目录。

为了方便,可以把 x64 Native Tools Command Prompt 配置到 Windows Terminal。

简单写一个Java类进行测试。

package org.lgq;

public class HelloLGQ {
  public static void main(String[] args) {
    System.out.println("Hello, DevLGQ!");
  }
}

然后使用javac编译成class文件。

# 编译
./javac .\org\lgq\HelloLGQ.java
# 运行看看有没有错误
./java org.lgq.HelloLGQ

class编译成本地应用,记得这时候要使用 x64 Native Tools Command Prompt,要不会编译报错。

native-image.cmd org.lgq.HelloLGQ

以上截图就说明编译成功了,之后直接输入 org.lgq.hellolgq.exe 运行看是否有问题。

简单对比下jvm和native的运行速度

测试代码

package org.lgq;

public class CountUppercase {
    static final int ITERATIONS = Math.max(Integer.getInteger("iterations", 1), 1);
    public static void main(String[] args) {
        String sentence = String.join(" ", args);
        for (int iter = 0; iter < ITERATIONS; iter++) {
            if (ITERATIONS != 1) System.out.println("-- iteration " + (iter + 1) + " --");
            long total = 0, start = System.currentTimeMillis(), last = start;
            for (int i = 1; i < 10_000_000; i++) {
                total += sentence.chars().filter(Character::isUpperCase).count();
                if (i % 1_000_000 == 0) {
                    long now = System.currentTimeMillis();
                    System.out.printf("%d (%d ms)%n", i / 1_000_000, now - last);
                    last = now;
                }
            }
            System.out.printf("total: %d (%d ms)%n", total, System.currentTimeMillis() - start);
        }
    }
}

编译运行

# 编译成class
./javac -encoding utf-8 .\org\lgq\CountUppercase.java
# 编译成 native
native-image.cmd org.lgq.CountUppercase
# 使用 openJDK 17 运行
java org.lgq.CountUppercase
# 使用 graalVM 运行
./java org.lgq.CountUppercase
# 运行native
org.lgq.countuppercase.exe

OpenJDK 运行结果

graalVM 运行结果

Native 运行结果

可以看到,native的运行速度反而更低了,graalVM性能反而是最好。造成这样结果的原因,有可能是native还是beta阶段的原因,也有可能是JVM的JIT在多次运行后进行了优化带来的性能提升。

SpringNative

SpringNative官方出品的,支持把Spring的应用编译成native应用,依赖 GraalVM 编译工具的。

相比于 JVM,native的方式可以为多种类型的工作负载提供更便宜和持续的托管,比如:微服务,FaaS等,非常适合容器和K8S,而且在启动速度,内存占用和即时峰值性能都有一定的优势。

官方文档

环境搭建

环境需求,需要Docker环境,如果是Linux系统,需要配置一个非root的用户,可以使用docker run hello-world来检查docker是否可用。
如果是mac系统,docker的内存分配至少要8GB,而且分配越多CPU核心越好。对于Windows的话,确保安装了WSL2,然后Docker WSL2是开启的,这样可以获取到更好的性能表现。

这里使用WSL2进行演示,关于WSL2的安装,可以看开发环境配置笔记中配置 WSL 的内容。

项目创建

创建项目,可以直接到https://start.spring.io/生成一个新的Spring应用。

如果在旧工程添加,注意,SpringBoot的版本是和 SpringNative 的版本有关联,比如 Spring Native 0.11.4就需要 Spring Boot 2.6.6 版本。我这边使用的是 SpringBoot 2.6.3 和 SpringNative 0.11.2 版本。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.3</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.2</version>
    </dependency>
</dependencies>

添加 Spring AOT 插件

<build>
    <plugins>
        <!-- ... -->
        <plugin>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-aot-maven-plugin</artifactId>
            <version>0.11.2</version>
            <executions>
                <execution>
                    <id>generate</id>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

添加支持编译Native

使用SpringBoot的Buildpacks构建工具可以直接把SpringBoot应用打包成容器镜像。

注意:tiny有更低的footprint和surface attack,也可以选 base(默认)或 full 。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <builder>paketobuildpacks/builder:tiny</builder>
            <env>
                <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
            </env>
        </image>
    </configuration>
</plugin>

默认下,GraalVM的版本会跟随 Buildpacks 的版本升级。而 Buildpacks 和 GraalVM 的版本是有对应关系的,例如 GraalVM 的 22.1.0,对应的 Buildpacks 版本是 7.1.0。如果你想使用固定的 GraalVM 版本,可以按照以下配置好。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!-- ... -->
        <image>
            <buildpacks>
                <buildpack>gcr.io/paketo-buildpacks/java-native-image:7.1.0</buildpack>
            </buildpacks>
        </image>
    </configuration>
</plugin>

添加Maven仓库。

<repositories>
    <repository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </repository>
</repositories>

在本地maven配置文件排除一些仓库id,要不会拉取不到某些依赖。maven配置文件如下。

<mirrors>
     <mirror>
        <id>public</id>
        <mirrorOf>*,!spring-snapshots,!spring-milestones,!spring-release</mirrorOf>
        <name>阿里云公共仓库</name>
        <url>https://maven.aliyun.com/repository/public</url>
     </mirror>
     <mirror>
        <id>google</id>
        <mirrorOf>*,!spring-snapshots,!spring-milestones,!spring-release</mirrorOf>
        <name>阿里云google仓库镜像</name>
        <url>https://maven.aliyun.com/repository/google</url>
     </mirror>
     <mirror>
        <id>central</id>
        <mirrorOf>*,!spring-snapshots,!spring-milestones,!spring-release</mirrorOf>
        <name>阿里云中央仓库镜像</name>
        <url>https://maven.aliyun.com/repository/central</url>
     </mirror>
     <mirror>
        <id>mavenorg</id>
        <mirrorOf>*,!spring-snapshots,!spring-milestones,!spring-release</mirrorOf>
        <name>mavne-org中央仓库</name>
        <url>http://repo.maven.apache.org/maven2</url>
     </mirror>
</mirrors>

编译运行

# 进入目标,使用mvn编译
mvn spring-boot:build-image

也可以直接在idea里点击编译。

编译成功,还是挺费时间的。

运行docker images可以看到打包好的镜像已经推到docker了。

# 启动
docker run --rm -p 8080:8080 nativedemo:0.0.1-SNAPSHOT

可以看到启动速度很快。

作为对比,JVM下启动就明显慢很多。

访问测试接口,也没有问题,正常返回数据。

如果要重新打包镜像的话,记得修改版本号或者先docker rmi containerId移除镜像再打包。

如果要停止运行的话,可以先用docker ps找出镜像id再使用docker stop containerId停止即可。

评论