在JDK10以前,能进行性能优化的即时编译器只有C2,但C2编译器的代码据说已经变得非常庞大且臃肿,同时伴随着云原生时代的到来,Java这种需要借助JDK才能运行的语言就显得格外臃肿,于是就有了Graal编译器的诞生,Graal编译器提供了非常强悍的能力,所以通常把Graal编译器与HotSpot合称为新一代的虚拟机——GraalVM。

GraalVM是一个高性能JDK发行版,旨在加速用Java和其他语言编写的应用程序的执行,并支持JavaScript、Ruby、Python和许多其他流行语言。

Python 怎么从 grafana 获取数据 graalvm python_java


从上面的图也可以看出来GraalVM并不是一个全新的异于HotSpot的虚拟机,而是在HotSpot的基础上,提供了功能非常强大的JIT编译器Graal Compiler。

GraalVM的设计目标是为了可以在不同的环境中运行程序,可以在JVM上运行,也可以直接将程序编译成独立的本地镜像(不需要JDK环境就能运行),或者将Java及本地代码模块集成为更大型的应用。

一、GraalVM下载和安装

GraalVM下载地址:https://www.oracle.com/downloads/graalvm-downloads.html,企业版目前也是免费的,可以使用企业版来学习使用。

选择GraalVM 20的版本,然后JDK版本选择8

Python 怎么从 grafana 获取数据 graalvm python_Image_02

在linux服务器上下载完压缩包以后,通过下面的命令解压:

tar -zxf graalvm-ee-java8-linux-amd64-20.3.5.tar.gz

然后配置环境变量

[root@lizhi graalvm-ee-java8-20.3.5]# vi /etc/profile
JAVA_HOME=/usr/local/graalvm/graalvm-ee-java8-20.3.5
PATH=/usr/local/graalvm/graalvm-ee-java8-20.3.5/bin:$PATH
export JAVA_HOME PATH

刷新配置文件,使其立即生效

[root@lizhi graalvm-ee-java8-20.3.5]# source /etc/profile

然后通过java -version查看

[root@lizhi graalvm-ee-java8-20.3.5]# java -version
java version "1.8.0_321"
Java(TM) SE Runtime Environment (build 1.8.0_321-07)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 20.3.5 (build 25.321-b07-jvmci-20.3-b28, mixed mode)

使用 GraalVM Enterprise,可以将 Java 字节码编译为特定于平台的、自包含的本机可执行文件(本机映像 Native Image),以实现更快的启动和更小的应用程序占用空间。

安装命令如下:

[root@lizhi graalvm]# gu install native-image
Downloading: Release index file from oca.opensource.oracle.com
Downloading: Component catalog for GraalVM Enterprise Edition 20.3.5 on jdk8 from oca.opensource.oracle.com
Skipping ULN EE channels, no username provided.
Downloading: Component catalog from www.graalvm.org
Processing Component: Native Image
The component(s) Native Image requires to accept the following license: Oracle GraalVM Enterprise Edition Native Image License
Enter "Y" to confirm and accept all the license(s). Enter "R" to the see license text.
Any other input will abort installation:  Y
Downloading: Contents of "Oracle GraalVM Enterprise Edition Native Image License" from oca.opensource.oracle.com
Downloading: Component native-image: Native Image  from oca.opensource.oracle.com
Installing new component: Native Image (org.graalvm.native-image, version 20.3.5)

然后通过gu list命令查看是否已经安装

[root@lizhi graalvm]# gu list
ComponentId              Version             Component name      Origin 
--------------------------------------------------------------------------------
js                       20.3.5              Graal.js            
graalvm                  20.3.5              GraalVM Core        
native-image             20.3.5              Native Image        oca.opensource.oracle.com

二、GraalVM初体验

下面通过一个简单的程序来体验一下GraalVM的强大之处。

创建一个简单的带有main()方法的类:

public class GraalVMTest {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

然后编译执行

[root@lizhi graalvm]# javac GraalVMTest.java 
[root@lizhi graalvm]# ll
total 408752
drwxr-xr-x 10 root root       252 May 25 17:12 graalvm-ee-java8-20.3.5
-rw-r--r--  1 root root 418550564 Mar 12 14:39 graalvm-ee-java8-linux-amd64-20.3.5.tar.gz
-rw-r--r--  1 root root       427 May 25 17:18 GraalVMTest.class
-rw-r--r--  1 root root       123 May 25 17:17 GraalVMTest.java
[root@lizhi graalvm]# java GraalVMTest 
Hello World

下面通过native-image将其打包成一个本地镜像文件

[root@lizhi graalvm]# native-image GraalVMTest
[graalvmtest:1217273]    classlist:   3,329.43 ms,  1.14 GB
[graalvmtest:1217273]        (cap):     920.18 ms,  1.62 GB
[graalvmtest:1217273]        setup:   4,131.87 ms,  1.62 GB
[graalvmtest:1217273]     (clinit):     150.62 ms,  1.71 GB
[graalvmtest:1217273]   (typeflow):   4,826.96 ms,  1.71 GB
[graalvmtest:1217273]    (objects):   3,083.11 ms,  1.71 GB
[graalvmtest:1217273]   (features):     173.80 ms,  1.71 GB
[graalvmtest:1217273]     analysis:   8,392.21 ms,  1.71 GB
[graalvmtest:1217273]     universe:     466.57 ms,  1.71 GB
[graalvmtest:1217273]      (parse):   1,589.48 ms,  1.71 GB
[graalvmtest:1217273]     (inline):   1,485.74 ms,  1.71 GB
[graalvmtest:1217273]    (compile):  17,884.23 ms,  2.08 GB
[graalvmtest:1217273]      compile:  21,618.90 ms,  2.08 GB
[graalvmtest:1217273]        image:     824.10 ms,  2.08 GB
[graalvmtest:1217273]        write:     195.61 ms,  2.08 GB
[graalvmtest:1217273]      [total]:  39,220.82 ms,  2.08 GB
[root@lizhi graalvm]# ll
total 412732
drwxr-xr-x 10 root root       252 May 25 17:12 graalvm-ee-java8-20.3.5
-rw-r--r--  1 root root 418550564 Mar 12 14:39 graalvm-ee-java8-linux-amd64-20.3.5.tar.gz
-rwxr-xr-x  1 root root   4074968 May 25 17:21 graalvmtest
-rw-r--r--  1 root root       427 May 25 17:18 GraalVMTest.class
-rw-r--r--  1 root root       123 May 25 17:17 GraalVMTest.java
[root@lizhi graalvm]# ./graalvmtest 
Hello World

这样就生成了一个可执行文件,相比于Java的编译过程,打包成镜像文件的过程就很繁琐,即便是这样一个简单的小程序就要近一分钟的时间。这个过程就是将Java字节码编译成特性平台、自包含的本机可执行文件(本地镜像 Native Image),以实现更快的启动和更小应用程序空间。

可以通过time命令比对一下上面两种程序执行方式的区别:

  • 通过JVM来运行
[root@lizhi graalvm]# time java GraalVMTest 
Hello World

real    0m0.063s
user    0m0.045s
sys     0m0.020s
  • 直接执行镜像文件
[root@lizhi graalvm]# time ./graalvmtest 
Hello World

real    0m0.002s
user    0m0.000s
sys     0m0.002s

通过比对就可以发现,通过本地镜像启动程序,启动速度就要快很多,阿里通过这种方式加快容器的启动速度,直接将启动速度提升20倍。

最重要的一点是,这种可执行文件是不需要JDK环境的,所以可以非常便捷的完成快速的容器化部署,这才符合云原生的要求。

将镜像文件拷贝到本地的虚拟机,本地虚拟机没有安装JDK,拷贝过来后还要先授权chmod 777,然后就可以运行

lizhi@Dog-li:~/test$ java -version

Command 'java' not found, but can be installed with:

sudo apt install default-jre            
sudo apt install openjdk-11-jre-headless
sudo apt install openjdk-8-jre-headless 

lizhi@Dog-li:~/test$ ./graalvmtest 
Hello World

三、C2编译器与Graal编译器的区别


Python 怎么从 grafana 获取数据 graalvm python_JVM_03

即时编译器是 Java 虚拟机中相对独立的模块,它主要负责接收 Java 字节码,并生成可以直接运行的二进制码。

但在Graal之前,以JDK8为例,即时编译器与Java虚拟机时紧耦合的,也就是说对即时编译器的更改需要重新编译整个Java虚拟机,这对于开发相对活跃的Graal来说是难以接收的。

为了让Java虚拟机与Graal解耦合,引入了Java虚拟机编译器接口(JVM Compiler Interface,JVMCI),将即时编译器的功能抽象成一个Java层面的接口。这样一来,在Graal所依赖的JVMCI版本不变的情况下,我们仅需要替换Graal 译器相关的jar包(Java 9 以后的 jmod 文件),便可完成对Graal的升级

Graal和C2最显著的区别就是:Graal是用Java写的,而C2是用C++写的。相对来说,Graal更加模块化,也更容易开发与维护,毕竟,连C2的开发者都不想去维护C2了。

Graal 的内联算法对新语法、新语言更加友好,例如 Java 8 的 lambda 表达式以及 Scala 语言。

以下面的例子来看这两者对于优化的效率:

public class Test {
    public static void main(String[] args) {
        String sentence = String.join(" ",args);
        long total = 0L,start = System.currentTimeMillis(),last = start;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10_000_000; j++) {
                total += sentence.chars().filter(Character::isUpperCase).count();
                if (j % 10_000_000 == 0){
                    long now = System.currentTimeMillis();
                    System.out.printf("%d (%d ms)%n",i,now-last);
                    last = now;
                }
            }
        }
        System.out.printf("total: %d (%d ms)%n ",total,System.currentTimeMillis() -start);
    }
}

上面这段代码循环内需要运行一千万次,会触发即时编译,所以下面通过对于在JDK8和GraalVM中运行这端代码的耗时:

JDK8:

[root@zhuyuzhu test]# java -version
openjdk version "1.8.0_302"
OpenJDK Runtime Environment (build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM (build 25.302-b08, mixed mode)
[root@zhuyuzhu test]# java Test 
0 (74 ms)
1 (717 ms)
2 (638 ms)
3 (616 ms)
4 (607 ms)
5 (600 ms)
6 (624 ms)
7 (594 ms)
8 (597 ms)
9 (621 ms)
total: 0 (6292 ms)

GraalVM:

[root@lizhi graalvm]# java -version
java version "1.8.0_321"
Java(TM) SE Runtime Environment (build 1.8.0_321-07)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 20.3.5 (build 25.321-b07-jvmci-20.3-b28, mixed mode)
[root@lizhi graalvm]# java Test 
0 (62 ms)
1 (124 ms)
2 (448 ms)
3 (16 ms)
4 (16 ms)
5 (15 ms)
6 (16 ms)
7 (15 ms)
8 (16 ms)
9 (15 ms)
total: 0 (759 ms)

从运行结果可以明显的看出来,Graal即时编译器的优化能力要比C2强很多。