新一代虚拟机GraalVM

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

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



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

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

一、GraalVM下载和安装

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

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


在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编译器的区别



即时编译器是 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强很多。

#Java##程序员#
全部评论

相关推荐

点赞 1 评论
分享
牛客网
牛客企业服务