之前写过一篇关于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的详细架构图:
我们可以很清楚的看到,JVM中既又JIT即时编译器,又有解释器!
4、JAVA的.class文件的完整执行过程:
可以认证阅读这两篇写得很好的文章:
其中:
-
Profiler进行热点探测:通过对代码的调用进行计数,统计出热点代码:调用次数非常多的代码;
-
同步编译:当场就进行编译,然后执行,编译完成后,将编译后的机器码放入到CodeCache中;;
-
异步编译:本次还是先使用解释执行,但是同时提交一个编译请求给后台,进行编译,编译完成后,将编译后的机器码放入到CodeCache中;
5、JVM已经这么强了,为什么还要引入AOT技术呢?
首先JVM暂时还不支持AOT,而是通过GraalVM来实现的!
-
现在的JAVA应用存在的问题:
-
java应用如果用jar,解释执行,热点代码才编译成机器码;
-
初始启动速度慢,初始处理请求数量少。
-
而在云原生时代,大型云平台,要求每一种应用都必须秒级启动。每个应用都要求效率高。
-
期望的效果:
-
java应用也能提前被编译成机器码,随时急速启动,一启动就急速运行,最高性能;
-
编译成机器码的好处:
-
启动和运行速度快;
-
别的服务器不再需要安装JAVA环境,直接运行。
原生镜像:native-image(机器码、本地镜像):把应用打包成能适配本机平台的可执行文件,在JVM中就需要使用到GraalVM;
6、GraalVM能够打包原生镜像的原理:
可以看到,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/
如果顺利安装成功,我们就可以在本地看到这样的一个应用服务:
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 的实战,我将在另一篇文章中重点讲解!