AOT技术与GraalVM的安装与简单使用

之前写过一篇关于GraalVM的文章,也可以参考:GraalVM——云原生时代的JVM


一、编译原理基础

1、AOT 与 JIT:

  • AOT:Ahead-of-Time(提前编译):程序执行前,全部被编译成机器码;

  • JIT:Just in Time(即时编译): 程序边编译,边运行;

具体的AOT和JIT的区别图解,在我的另一篇文章中有详细描述!

AOT JIT
优点 1.速度快,优化了运行时编译时间和内存消耗

2.程序初期就能达最高性能

3.加快程序启动速度

1.具备实时调整能力

2.生成最优机器指令

3.根据代码运行情况优化内存占用

缺点 1.程序第一次编译占用时间长

2.牺牲高级语言一些特性

1.运行期边编译速度慢

2.初始编译不能达到最高性能

2、编译型语言 与 解释型语言:

  • 编译型语言:编译器Complier,先整体编译(源代码 ——> 01机器码),再执行;—— 如果一行出错,整个文件都运行不起来(c);

  • 解释型语言:解释器Interpreter,读一行,解释一行,执行;—— 如果一行出错,前面行的代码依然会被执行(python);

编译器 解释器
机器执行速度 快,因为源代码只需被转换一次 慢,因为每行代码都需要被解释执行 
开发效率 慢,因为需要耗费大量时间编译 快,无需花费时间生成目标代码,更快的开发和测试 
调试 难以调试编译器生成的目标代码  容易调试源代码,因为解释器一行一行地执行 
可移植性(跨平台) 不同平台需要重新编译目标平台代码 同一份源码可以跨平台执行,因为每个平台会开发对应的解释器 
学习难度 相对较高,需要了解源代码、编译器以及目标机器的知识 相对较低,无需了解机器的细节 
错误检查 编译器可以在编译代码时检查错误 解释器只能在执行代码时检查错误 
运行时增强 可以动态增强 

3、J    VM中既有 JIT即时编译器 又有 解释器:

所以,JAVA比较贪心,既想要编译型语言执行快的特性,又想要解释型语言强大的动态特性

JVM的详细架构图:

1689229149939773.png

我们可以很清楚的看到,JVM中既又JIT即时编译器,又有解释器!

4、JAVA的.class文件的完整执行过程:

可以认证阅读这两篇写得很好的文章:

1689229546557612.png

其中:

  • Profiler进行热点探测:通过对代码的调用进行计数,统计出热点代码:调用次数非常多的代码;

  • 同步编译:当场就进行编译,然后执行编译完成后,将编译后的机器码放入到CodeCache中;

  • 异步编译:本次还是先使用解释执行,但是同时提交一个编译请求给后台,进行编译,编译完成后,将编译后的机器码放入到CodeCache中;

5、JVM已经这么强了,为什么还要引入AOT技术呢?

首先JVM暂时还不支持AOT,而是通过GraalVM来实现的!

  • 现在的JAVA应用存在的问题:

    • java应用如果用jar,解释执行,热点代码才编译成机器码;

    • 初始启动速度慢,初始处理请求数量少。

    • 而在云原生时代,大型云平台,要求每一种应用都必须秒级启动。每个应用都要求效率高。

  • 期望的效果:

    • java应用也能提前被编译成机器码,随时急速启动,一启动就急速运行,最高性能;

  • 编译成机器码的好处:

    • 启动和运行速度快;

    • 别的服务器不再需要安装JAVA环境,直接运行。

原生镜像:native-image(机器码、本地镜像):把应用打包成能适配本机平台的可执行文件,在JVM中就需要使用到GraalVM;

6、GraalVM能够打包原生镜像的原理:

1689231016304800.png

可以看到,GraalVM需要打包原生镜像,还是依靠于操作系统本地的集成环境的,如Windows和Linux上的C++环境!


二、GraalVM 和 Native-Image的安装(Windows)

GraalVM 和 及Native-Image命令的下载地址:

(最初写文章时使用的是Graalvm21,后期将版本改为了GraalVM22):

https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-22.3.2

我个人的网盘链接:

————

————

1、前提条件:Window系统已经安装了C++集成环境:VisualStudio

下载地址:https://visualstudio.microsoft.com/zh-hans/free-developer-offers/

1689231211921381.png

1689231316291130.png

1689231356580140.png

如果顺利安装成功,我们就可以在本地看到这样的一个应用服务:

1689231540774112.png

2、安装GraalVM:

将下载的GraalVM压缩包解压到任意位置,但是根据约定,我们一般放在: C:\Program Files\Java\ 目录下

然后配置JAVA_HOME环境变量(必须):

JAVA_HOME : C:\Program Files\Java\graalvm-ce-java17

配置完成后,我们就可以看到以下命令结果:

C:\Users\admin>java -version
openjdk version "17.0.4" 2022-07-19
OpenJDK Runtime Environment GraalVM CE 21.3.3.1 (build 17.0.4+8-jvmci-21.3-b20)
OpenJDK 64-Bit Server VM GraalVM CE 21.3.3.1 (build 17.0.4+8-jvmci-21.3-b20, mixed mode, sharing)
 
C:\Users\admin>gu --version
GraalVM Updater 21.3.3.1

3、使用gu命令安装 native-image 命令(必须):

# 需要管理员身份运行:
C:\Windows\System32>gu install --file D:\Program\jdk\native-image-installable-svm-java17-windows-amd64-21.3.3.1.jar
Installing new component: Native Image (org.graalvm.native-image, version 21.3.3.1)

## 如果网络允许,我们可以直接在线安装
C:\Windows\System32>gu install native-image
Downloading: Component catalog from www.graalvm.org
Processing Component: Native Image
Downloading: Component native-image: Native Image  from github.com
Installing new component: Native Image (org.graalvm.native-image, version 21.3.3.1)
 
C:\Windows\System32>native-image --version
GraalVM 21.3.3.1 Java 17 CE (Java Version 17.0.4+8-jvmci-21.3-b20)

## 查看gu已经安装的列表:
C:\Windows\System32>gu list
ComponentId        Version        Component name      Stability           Origin
------------------------------------------------------------------------------------
graalvm            21.3.3.1       GraalVM Core        Supported
js                 21.3.3.1       Graal.js            Supported
native-image       21.3.3.1       Native Image        Early adopter       github.com

至此,Windows版的GraalVM和native-image环境就安装完成了!


三、GraalVM 和 Native-Image的安装(Linux)

1、安装gcc等编译环境:

yum install gcc glibc-devel zlib-devel -y

# 安装一个文件传输工具(非必须)
yum install lrzsz -y
rz:本地到服务器
sz:服务器到本地

2、下载GraalVM 和 Native-Image的压缩文件,拷贝到服务器上,解压:

[root@jiguiquan ~]# tar -zxvf graalvm-ce-java17-linux-amd64-21.3.3.1.tar.gz -C /opt/software/

[root@jiguiquan ~]# cd /opt/software/

[root@jiguiquan software]# mv graalvm-ce-java17-21.3.3.1/ graalvm21-java17/

[root@jiguiquan software]# ls graalvm21-java17/
bin  conf  GRAALVM-README.md  include  jmods  languages  legal  lib  LICENSE.txt  release  THIRD_PARTY_LICENSE.txt  tools

3、配置JAVA环境变量:

[root@jiguiquan graalvm21-java17]# vim /etc/profile.d/myenv.sh
# 内容如下:
export JAVA_HOME=/opt/software/graalvm21-java17
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/jre/lib

## 使环境变量生效
[root@jiguiquan graalvm21-java17]# source /etc/profile

4、查看GraalVM和gu命令是否安装成功:

[root@jiguiquan graalvm21-java17]# java -version
openjdk version "17.0.4" 2022-07-19
OpenJDK Runtime Environment GraalVM CE 21.3.3.1 (build 17.0.4+8-jvmci-21.3-b20)
OpenJDK 64-Bit Server VM GraalVM CE 21.3.3.1 (build 17.0.4+8-jvmci-21.3-b20, mixed mode, sharing)

[root@jiguiquan graalvm21-java17]# gu --version
GraalVM Updater 21.3.3.1

5、使用gu安装native-image命令:

[root@jiguiquan ~]# gu install --file native-image-installable-svm-java17-linux-amd64-21.3.3.1.jar 
Processing Component archive: native-image-installable-svm-java17-linux-amd64-21.3.3.1.jar
Installing new component: Native Image (org.graalvm.native-image, version 21.3.3.1)

[root@jiguiquan ~]# native-image --version
GraalVM 21.3.3.1 Java 17 CE (Java Version 17.0.4+8-jvmci-21.3-b20)

[root@jiguiquan ~]# gu list
ComponentId       Version         Component name        Stability            Origin 
----------------------------------------------------------------------------------------------
graalvm           21.3.3.1        GraalVM Core          Supported                     
js                21.3.3.1        Graal.js              Supported                     
native-image      21.3.3.1        Native Image          Early adopter

至此,Linux版的GraalVM和native-image环境就安装完成了!


四、简单使用GraalVM将本地的程序打包成二进制可执行文件:

1、首先,我需要一个简单的Maven项目:

项目太简单,就不展示了,就一个入口文件需要特别指定一下:

<?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.jiguiquan.www</groupId>
    <artifactId>graalvmhw</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <classesDirectory>target/classes/</classesDirectory>
                    <archive>
                        <manifest>
                            <mainClass>com.jiguiquan.www.GraalvmHw</mainClass>
                            <useUniqueVersions>false</useUniqueVersions>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                        <manifestEntries>
                            <Class-Path>.</Class-Path>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

package打包完成后的jar包,我们使用java -jar 运行看下效果:

E:\Study-Code\jdk17Sb3\graalvmhw\target>java -jar graalvmhw-1.0.jar
GraalVM Hello World!

具备测试条件!

2、使用native-image命令将上面项目打包成的jar包,编译成.exe可执行文件(Windows):

编译的命令格式如下:

# 保险起见,使用native工具进行执行,而不是cmd
native-image -cp xxx.jar org.example.App
native-image [options] -jar jarfile [executable name]

使用native工具,进入到我们项目的target目录:

C:\Program Files\Microsoft Visual Studio\2022\Community>e:

E:\>cd E:\Study-Code\jdk17Sb3\graalvmhw\target

E:\Study-Code\jdk17Sb3\graalvmhw\target>dir
 驱动器 E 中的卷是 新加卷
 卷的序列号是 04DF-6A93

 E:\Study-Code\jdk17Sb3\graalvmhw\target 的目录

2023-07-13  15:34    <DIR>          .
2023-07-13  15:34    <DIR>          ..
2023-07-13  15:34    <DIR>          classes
2023-07-13  15:34    <DIR>          generated-sources
2023-07-13  15:34             2,565 graalvmhw-1.0.jar
2023-07-13  15:34    <DIR>          maven-archiver
2023-07-13  15:34    <DIR>          maven-status
               1 个文件          2,565 字节
               6 个目录 13,985,431,552 可用字节

执行如下命令,完成编译打包(多个方法):

# 方法1:最方便的,通过通过jar包编译
native-image -jar graalvmhw-1.0.jar zidan1

## 方法2:也是通过jar包编译
native-image -cp graalvmhw-1.0.jar com.jiguiquan.www.GraalvmHw zidan2

### 方法3:通过.class文件进行编译
native-image -cp classes com.jiguiquan.www.GraalvmHw zidan3

3、执行上面生成的.exe文件,查看结果:

E:\Study-Code\jdk17Sb3\graalvmhw\target>zidan1.exe
GraalVM Hello World!

E:\Study-Code\jdk17Sb3\graalvmhw\target>zidan2.exe
GraalVM Hello World!

E:\Study-Code\jdk17Sb3\graalvmhw\target>zidan3.exe
GraalVM Hello World!

可见上面生成的结果都是可以直接运行的!

4、我们再去Linux环境上试一下,看看编译是否依然可以成功(Linux)

[root@jiguiquan jiguiquan]# native-image -jar graalvmhw-1.0.jar zidan4
[zidan4:2202]    classlist:   1,781.41 ms,  0.96 GB
[zidan4:2202]        (cap):     649.46 ms,  0.96 GB
[zidan4:2202]        setup:   2,316.49 ms,  0.96 GB
[zidan4:2202]     (clinit):     180.48 ms,  1.76 GB
[zidan4:2202]   (typeflow):   5,205.03 ms,  1.76 GB
[zidan4:2202]    (objects):   8,393.40 ms,  1.76 GB
[zidan4:2202]   (features):     764.35 ms,  1.76 GB
[zidan4:2202]     analysis:  14,946.83 ms,  1.76 GB
[zidan4:2202]     universe:   1,300.00 ms,  2.34 GB
[zidan4:2202]      (parse):   1,035.49 ms,  2.34 GB
[zidan4:2202]     (inline):   1,192.88 ms,  2.34 GB
[zidan4:2202]    (compile):  12,449.22 ms,  2.37 GB
[zidan4:2202]      compile:  15,424.13 ms,  2.49 GB
[zidan4:2202]        image:   1,505.50 ms,  2.49 GB
[zidan4:2202]        write:     253.85 ms,  2.49 GB
[zidan4:2202]      [total]:  37,791.14 ms,  2.49 GB
# Printing build artifacts to: /jiguiquan/zidan4.build_artifacts.txt

[root@jiguiquan jiguiquan]# ll
total 14600
-rw-r--r-- 1 root root     2565 Jul 13 15:34 graalvmhw-1.0.jar
-rwxr-xr-x 1 root root 14939848 Jul 13 16:20 zidan4
-rw-r--r-- 1 root root       21 Jul 13 16:20 zidan4.build_artifacts.txt

5、测试下,此二进制文件是否也可以正常运行:

[root@jiguiquan jiguiquan]# ./zidan4 
GraalVM Hello World!

至此,简单版本的GraalVM + Native-Image打包“原生镜像”的测试就算完成了!


五、写在最后

1、使用GraalVM + Native-Image编译二进制文件真的如此简单吗?

—— 只需要提供编译环境,然后提供java项目的jar包,即可完成编译打包?

    答案是否定的,这种方式只能适合简单的项目,

    因为我们在文章最开始讲过,AOT和JIT都属于编译型语言的过程,但是编译型语言是没有动态特性的(如反射),而反射是java中非常重要的一个功能,几乎我们用的所有框架中都大量地使用了反射,所以肯定是没有办法直接将这些项目的jar包简单打包成可直接执行的二进制文件的!

2、那么我们用Spring写的代码就不能通过AOT技术,编译打包成可执行文件了?

答案也是否定的,不是不可以编译打包,而是需要解决一些问题:

  • 动态能力丢失的问题:

    • 解决方案:提前告知native-image工具,反射会用到那些方法,构造器等!(Springboot3.0就提供了一些注解,专门处理这个问题)

    // 有兴趣可以学习一下这个注解
    public @interface ImportRuntimeHints {
        Class<? extends RuntimeHintsRegistrar>[] value();
    }
  • 配置文件放在哪?—— jar包本质是个压缩包,里面当然可以放置配置文件,但是.exe可执行文件,里面怎么好放置配置文件呢?

    • 解决方案:提前告知native-image工具,我们的配置文件在哪儿,你给特殊处理下,比如外部的某个路径下,或者配置中心中!

二进制中不能包含的,需要动态的,都得提前处理!

SpringBoot3(其实是Spring Native)就帮我们做了这样的工作,适配了AOT!

但是不是所有的框架,都适配了AOT;

关于GraalVM + Native-Image + SpringBoot3 的实战,我将在另一篇文章中重点讲解!

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐