
作者|ChrisSeaton
译者|无明
编辑|张婵
不久前Oracle发布了GraalVM,一套通用型虚拟机,能执行各类高性能与互操作性任务,并在无需额外成本的前提下允许用户构建多语言应用程序。
GraalVM包含了很多不同的部分,我们将列出GraalVM的一些不同的特性,并展示它的用途。
高性能Java
占用内存小、启动速度快的Java
组合JavaScript、Java、Ruby和R语言
在JVM上运行本地语言
适用于所有编程语言的工具
扩展基于JVM的应用程序
扩展本地应用程序
将Java代码作为本地库
数据库中的polyglot
创建自己的语言
由于篇幅过长,我们只在本文中详细分享前6大用途。关于GraalVM的更多精彩内容,也可订阅或试读OracleLabs的高级研究员郑雨迪的专栏《深入拆解Java虚拟机》。郑雨迪会在单独拿出一个模块的内容来分享Oracle的虚拟机黑科技,科普GraalVM的各个组成部分,其中包括编译器Graal,语言实现框架Truffle,以及支持Ahead-of-Time(ATO)编译的SubstrateVM。
我们可以使用()来重现本文所述的内容。我是在macOS上运行GraalVM企业版,不过在Linux上运行GraalVM社区版也是一样的。文中运行的代码可以从。
安装
我从,并将它放到$PATH路径中。
$gitclone$cdfoo$=/bin:$PATHonLinux
GraalVM内置了JavaScript,并带有一个叫作gu的软件包管理器,可用它来安装其他语言。我已经安装了从GitHub下载的Ruby、Python和R语言。
$$$
我们可以通过运行java或js来获得这些运行时的版本信息。
$java-versionjavaversion"1.8.0_161"Java(TM)SERuntimeEnvironment(_161-b12)(,mixedmode)$()
高性能
GraalVM中的Graal得名于Graal编译器。Graal是一种“万能”编译器,也就是说,虽然它是单一的实现,却可以用于很多用途。例如,我们可以使用Graal进行预编译(ahead-of-time)和即时编译(just-in-time),也可用于编译多种编程语言。
我们可以将Graal简单地用作JavaJIT编译器。
以下的示例程序将会输出一篇文档的前十个单词,它使用了Stream和Collector等Java语言特性。
;;;;;;;publicclassTopTen{publicstaticvoidmain(String[]args){(args).flatMap(TopTen::fileLines).flatMap((("\\b"))).map(("[^a-zA-Z]","")).filter().map().collect((,)).((a,b)--()).limit(10).forEach(("%s=%d%n",,));}privatestaticStreamStringfileLines(Stringpath){try{((path));}catch(IOExceptione){thrownewRuntimeException(e);}}}GraalVM包含了一个javac编译器,但在本例中,它与标准的编译器并没有什么区别。因此,如果你愿意,也可以使用系统的javac。
$
如果我们运行GraalVM提供的java命令,将会自动调用GraalJIT编译器,不需要做额外的配置。我使用time命令来获得整个程序从开始到运行结束所花费的时间,而不是进行复杂的微基准测试。我使用了大量的输入,这样就不用去纠结几秒钟的差别。我使用的文件大小为150MB。
$$=502701ut=392657in=377651et=352641id=317627eu=317627eget=302621vel=300120a=287615sit=282613
Graal是使用Java开发的,而其他大多数JavaJIT编译器是使用C++开发的。我们因此能够比其他编译器更快地改进它,而且它具备了强大的优化功能,比如HotSpot标准JIT编译器中所没有的部分转义分析功能。这项优化功能可以让Java程序的运行速度明显加快。
为了与不使用GraalJIT编译器时的速度进行比较,我使用-XX:-UseJVMCICompiler标记来运行程序。JVMCI是Graal和JVM之间的接口。当然,我们也可以拿它与标准的JVM进行比较。
$timejava-XX:-=502701ut=392657in=377651et=352641id=317627eu=317627eget=302621vel=300120a=287615sit=282613
结果显示,使用Graal运行程序的时间大约是标准HotSpot的四分之三。在习惯于将单个位数的百分比性能增长视为显著改进的今天,这个数字算得上是一个巨大的提升。
Twitter是在生产环境中使用Graal的公司之一,他们表示,Graal确确实实为他们省下了不少钱。Twitter使用Graal来运行Scala应用程序。因为Graal执行的是JVM字节码,因此适用于任何基于JVM的语言。
这是GraalVM的第一个用途——将它作为Java应用程序的一个更好的JIT编译器。
占用内存小、启动速度快的Java
Java对于长时间运行的进程来说是相当强大的,但短时间运行的进程可能会因较长的启动时间和较高的内存占用而饱受其苦。
例如,如果我们使用更小的输入来运行相同的应用程序(文件大小约为1KB,而不是150MB),似乎需要花费更长的时间,并且需要60MB的内存。我们使用-l选项打印出它所消耗的内存和运行时间。
$$/usr/bin/:///$
与在JVM上运行相同的程序相比,可执行文件的启动速度快了一个数量级,使用的内存少了一个数量级。它的速度非常快,快到让你注意不到在命令行上所花费的时间,而且感觉不到停顿。
$/usr/bin/time-l./=6sit=6amet=6mauris=3volutpat=3vitae=3dolor=3libero=3tempor=2suscipit=20.02
不过,native-image这个工具也存在一些约束,比如所有的类在编译期间都必须可用。除此之外,在反射方面也存在一些限制。除了基本的编译功能之外,它还提供了额外的高级特性,即在编译期间运行静态初始化器,以便在加载应用程序时可以少做一些事情。
这是GraalVM的第二个用途——以低内存占用和快速启动来运行现有的Java程序。它为我们省去了一些配置问题,比如如何在运行时查找正确的jar文件。它还能让Docker镜像的体积变得更小。
组合JavaScript、Java、Ruby和R语言
除了Java,GraalVM还包含了JavaScript、Ruby、R语言和Python的实现。它们都是使用一个叫作Truffle的语言实现框架开发的,Truffle让实现简单且高性能的语言解释器成为可能。在使用Truffle开发语言解释器时,会自动使用Graal作为JIT编译器。因此,Graal不仅是Java的JIT编译器和预编译器,也可以是JavaScript、Ruby、R语言和Python的JIT编译器。
GraalVM中的语言旨在成为现有语言的直接替代品。例如,我们可以安装一个模块:
$npminstall--globalcolor+color@3.0.0
我们可以使用此模块编写一个小程序,将RGBHTML颜色转换为HSL:
varColor=require('color');(2).forEach(function(val){print(Color(val).);});然后用常规的方式运行它:
$'#42aaf4'hsl(204.89999999999998,89%,60.8%)
GraalVM提供了一个API,用以在一门语言中运行另一门语言的代码。因此,我们可以使用多种语言来开发一个应用程序。
我们可能希望用一种语言开发应用程序的主要部分,同时又使用另一种语言的软件包。例如,假设我们用开发一个将CSS颜色名称转换为十六进制数的应用程序,但又希望使用Ruby颜色库来完成转换。
varexpress=require('express');varapp=express;color_rgb=('ruby',`require'color'Color::RGB`);('/css/:name',function(req,res){color=color__name().('h1style="color:'+color+'"'+color+'/h1');});(8080,function{('servingathttp://localhost:8080')});我们以字符串形式提供了一小串Ruby代码——导入必要的库,然后返回一个Ruby对象。要在Ruby中使用这个对象,通常需要这样:Color::_name(name).html。而在我们的例子中,我们是在JavaScript里调用这些方法,即使它们是Ruby的对象和方法。并且,我们传给它一个JavaScript字符串,然后把结果连接起来,结果里包含了Ruby字符串和其他JavaScript字符串。
下面安装Ruby和JavaScript的依赖项。
$geminstallcolorFetching:(100%)$npminstallexpress+express@4.16.2
然后,我们需要使用以下几个选项来运行node:--polyglot表示我们想要访问其他语言,--jvm表示要使用JVM,因为默认情况下node本地镜像只包含了JavaScript。
$://localhost:8080
然后在浏览器中打开http://localhost:8080/css/aquamarine。除了aquamarine,也可以使用其他颜色名称。

图片:!thumbnail接下来让我们尝试使用更多的语言和模块。
对于任意大的整数,JavaScript并没有很好的解决方案。我发现了几个像big-integer这样的模块,但这些模块的性能并不好,因为它们将数字的组成部分存储为JavaScript浮点数。Java的BigInteger性能更好,所以我们用它来做一些任意大的整数运算。
JavaScript也不提供对图形绘制的内置支持,而R语言却对此提供了很好的支持。我们使用R语言的svg模块绘制三角函数的三维散点图。
我们可以使用GraalVM的polyglotAPI,并将其他语言代码的运行结果添加到JavaScript中。
constexpress=require('express')constapp=expressconstBigInteger=('')('/',function(req,res){vartext='!br'//UsingJavastandardlibraryclassestext+=(10).pow(100).add((43)).toString+'br'//UsingRinteroperabilitytocreategraphstext+=('R',`svg;require(lattice);x-1:100y-sin(x/10)z-cos(x^1.3/(runif(1)*5+10))print(cloud(x~y*z,main="cloudplot"))grDevices:::`);(text)})(3000,function{('Exampleapplisteningonport3000!')})在浏览器中打开http://localhost:3000/查看结果。

图片:!thumbnail这是GraalVM的第三个用途——运行使用多种语言编写的程序,并组合使用这些语言的模块。我认为这是语言和模块的大众化——你可以为你的问题选择任何一门合适的语言以及任何你想要的库。
在JVM上运行本地语言
GraalVM也支持C语言,GraalVM可以像运行JavaScript和Ruby之类的语言一样运行C代码。
实际上,GraalVM通过运行LLVM位码的方式来支持C语言,而不是直接运行C代码。也就是说,我们可以将现有工具与C语言一起使用,还可以使用其他可输出LLVM的语言,例如C++、Fortran和未来可能出现的其他语言。为了简化演示,我使用了由StephenMcCamant维护的gzip的单文件版本。为简单起见,它只是将gzip源代码和autoconf配置连成一个单独的文件。我还需要修改一些东西才能让它在macOS上运行起来,但不能在GraalVM上运行。
然后我们使用标准clang(LLVMC语言编译器)来编译它,并把它编译成LLVM位码,而不是本地汇编代码,这样就可以在GraalVM上运行。我使用的是。
$
然后我们使用lli命令(LLVM位码解释器)直接在GraalVM上运行编译后的位码。我们先使用系统gzip来压缩文件,然后使用运行在GraalVM上的gzip进行解压缩。
$$$$
GraalVM中的Ruby和Python实现也使用了这种技术来运行C扩展。也就是说,我们可以在虚拟机内部运行C扩展,同时保持高性能。
这是GraalVM的第四个用途——运行使用C和C++等本地语言编写的程序,并且还可以运行C语言扩展,而像JRuby这样的JVM是无法做到这点的。
适用于所有编程语言的工具
如果你使用Java编程,可能已经习惯了使用那些高质量的工具,比如IDE、调试器和分析器,但并非所有的编程语言都有这么好用的工具。不过如果你是在GraalVM中使用某种语言,就可以获得这样的工具。
所有GraalVM语言(目前除了Java)都是使用Truffle框架实现的,所以一个功能只要开发一次(比如调试器),就可以用在所有语言上。
为了试验这个功能,我们开发了一个简单的FizzBuzz程序。它将内容输出到屏幕上,逻辑分支很清晰,只需要进行少量迭代,我们因此可以很容易地设置断点。我们先使用JavaScript来实现。
functionfizzbuzz(n){if((n%3==0)(n%5==0)){return'FizzBuzz';}elseif(n%3==0){return'Fizz';}elseif(n%5==0){return'Buzz';}else{returnn;}}for(varn=1;n=20;n++){print(fizzbuzz(n));}我们可以像平常一样使用GraalVM运行这个JavaScript程序。
$
我们也可以用--inspect标记来运行它,它会输出一个可以在Chrome中打开的链接,并在调试器中暂停程序的运行。
$,openthefollowingURLinChrome:chrome-devtools://devtools/bundled/?ws=127.0.0.1:9229/6c478d4e-1350b196b409
然后我们在FizzBuzz代码中设置一个断点,并继续执行。在跳过断点后,我们可以看到n的值,然后继续,或者查看调试接口的其余部分。

这个标志也可用在Python、Ruby和R语言上。我就不展示每个程序的源代码了,它们的运行方式都是一样的。
$

$

$

你可能对Java的另一个工具VisualVM很熟悉,它为我们提供了一个用户界面,可以将它连接到本机或网络上的某个JVM,以便检查各种问题,比如内存和线程的使用情况。
GraalVM也包含了带有标准jvisualvm命令的VisualVM。
$jvisualvm/dev/
如果我们在运行TopTenJava应用程序的同时运行jvisualvm,就可以看到内存随时间变化的情况,或者我们可以做一些其他的事情,比如进行堆转储,然后检查堆中的对象类型。
$

我写了一个用来生成垃圾的Ruby程序。
require'erb'x=42template=:%=x%(binding)
如果使用VisualVM来运行标准的JVM语言(如JRuby),则会感到失望,因为你看到的是底层的Java对象,而不是所使用语言的对象的信息。
如果我们使用Ruby的GraalVM版本,VisualVM将会识别出Ruby对象。我们需要使--jvm选项来启动VisualVM,因为它不支持本地版本的Ruby。
$
如果有需要的话,我们还可以看到底层Java对象的堆转储,或者在Summary下选择RubyHeap来查看Ruby对象。

图片:!thumbnailTruffle框架就像是编程语言和工具之间的一种联系。如果我们使用Truffle来开发自己的编程语言,并基于Truffle的工具API开发各种工具(比如调试器),那么开发出来的工具可以适用于每一种语言,而且只需要开发一次即可。
这是GraalVM的第五个用途——为编程语言提供高质量的工具。
扩展基于JVM的应用程序
除了可用作独立语言实现和用于多语言编程,这些语言和工具也可以嵌入到Java应用程序中。新的可用于加载和运行其他语言的代码。
;;publicclassExtJava{publicstaticvoidmain(String[]args){Stringlanguage="js";try(Contextcontext=(true).build){for(Stringarg:args){if(("-")){language=(1);}else{Valuev=(language,arg);(v);}}}}}如果我们使用了GraalVM的javac和java命令,那么…就已经存在于类路径中,可以直接编译并运行代码,不需要使用任何额外的标记。
$$javaExtJava'14+2'16$javaExtJava-js'(14)'3.7416573867739413$javaExtJava-python'[2**nforninrange(0,8)]'[1,2,4,8,16,32,64,128]$javaExtJava-ruby'[4,2,3].sort'[2,3,4]
这些版本的语言与通过使用node和ruby这些命令运行的代码一样,都具备了很高的性能。
这是GraalVM的第六个用途——作为在Java应用程序中嵌入不同语言的接口。我们可以借助polyglotAPI来获取其他语言的对象,并将它们用作Java接口,实现其他复杂的操作。
结论
GraalVM支持多种新功能,它是一个平台,我们可以在这个平台上构建更强大的语言和工具,并将它们放入更多的环境中。无论程序在哪里运行,或者使用了哪种语言,它都可以让我们选择所需的语言和模块。
进阶必备专栏:《深入拆解Java虚拟机》。扫码,即可试读此专栏的前三篇文章
免责声明:本文章如果文章侵权,请联系我们处理,本站仅提供信息存储空间服务如因作品内容、版权和其他问题请于本站联系