🏆「作者推荐」Java 技术专题 -JDK/JVM 的新储君—GraalVM 和 Quarkus
GraalVM 介绍
今天我们来讲讲整个 Java 生态下相当有分量的一位角儿——GraalVM:GraalVM官方网站,Quarku官方文档
GraalVM 是用 Java 实现的基于 HotSpot/OpenJDK 的 JVM 和 JDK。它支持额外的编程语言和运行范式,例如对 Java 应用程序 AOT,从而实现快速启动和低内存占用。
GraalVM 的三大特点:
GraalVM 可以代替 JDK、JVM 之前的工作。
GraalVM 除了支持 Java,也支持多种语言。
GraalVM 可以对应用 AOT,也就是把程序直接编译成二进制,从而提升启动速度、改进内存使用。
了解完这些背景知识,我们再看看官网怎么说。
在各种地方跑起来都更快。提升应用的吞吐并减少延迟、把应用编译成独立本机的二进制程序、无缝使用多种语言和库。
性能有多强
多语言互相调用好使不
编译二进制香不香
性能有多强
GraalVM 框架的测试表现供大家参考,数据有点夸张哈,但是大趋势肯定是不会错的。
横轴表示时间,可以明显看出在 GraalVM 的加持下,Quarkus 的启动后首次响应时间大概提升了 50 倍左右(0.016vs0.943),这算是一个相当恐怖的数据了。
上面的数据证明通过 GraalVM 编译后,启动及响应速度是大幅跃进了。那么内存使用呢,这个也是云原生比较关注的点。请看下图:
大概有个 5 倍左右的改进吧,应该说相当不错了。如果说前面的响应速度影响的还是用户体验,那 8G 内存的服务器跟 40G 内存的服务器,那省下来的可是真金白银啊。
聪明的小伙伴可能会说了,你上面的数据强是强,但是万物互联时代,主要看吞吐,对别的技术栈来说,上面的两个维度数据即使不理想,也不能算硬伤。吞吐强,才是真的强。我们一起来看下图:
来自 TechEmpower 的数据测试。我只截取了使用 JavaScript 语言相关技术(都是后端哦)的成绩。先交代下背景,TechEmpower 准备了同样的硬件环境,然后用不同的语言和框架来做同一件事情(提供后端 HTTP API),并对它们进行压力测试记录下来成绩。所有参与测试的代码都是开源的,可以在 GitHub 上找到。当然这种“跑分”化的场景跟我们实际项目运行的情况肯定是千差万别的,TechEmpower 考虑到实际情况的复杂性,准备了不同的测试场景,如图:
其中 Fortunes 要从数据库取数然后在服务端进行数据排序,我觉得还是比较有代表性,所以上面的“跑分”截图就用的 Fortunes 的成绩。
现在我们来具体看下成绩,TechEmpower 的规则是以第一名的得分值作为基准,也就是 100%。第一名 es4x 框架实际得分为 237751,这个数字的含义截图中有提到——responses per second,也就是每秒响应数高达 23 万多,应该是个非常恐怖的数字了。我们可以对比看下 nodejs 的得分,也有 91,799 的每秒响应数,其实也很惊人,但是成绩也才有 es4x 的 38.6%。如果你仔细看的话,会发现 es4x 的 100%以及 nodejs 的 38.6%数字后面还分别有两个百分比,这个数字代表其得分跟所有本次参与的技术及框架中最强者的比较。至于最强者是谁,我就卖个关子,有兴趣的朋友可以自己去官方网站查看。
好了,现在你应该同意 es4x 是一个相当有性能优势的技术了。而 es4x 之所以能取得如此不俗的成绩,就是因为它用到了我们今天谈论的主角——GraalVM。
TechEmpower 的 GitHub 上能看到说明,指出了这次测试是基于 GraalVM 的。略显遗憾的就是,TechEmpower 并没有测试一版不依赖 GraalVM 的 es4x,其实 es4x 是一种跑在 JDK 上的 JavaScript 技术(如果你对 JDK 上跑 JavaScript 感到惊奇,可以查看你不知道的 Java),所以 es4x 跟 GraalVM 不是强关联的,只是 es4x 官方认为 GraalVM 性能更好,所以不论 es4x 的官网还是 TechEmpower 的跑分,都是把 GraalVM 作为了第一选择。
至此,我已经花了不少篇幅来介绍 GraalVM 的性能优势,相信你对它的性能水平有了一个大概的认识。前面的内容基本是拿来主义,重度依赖了互联网上已有的数据成果,下面我们开始第二个议题,真刀真枪的写点代码试一试。
多语言互相调用好使不
先来看看 GraalVM 支持哪些语言:
应该说有点意思,支持的语言还真不少。那我们就来动手试试吧。
安装 GraalVM
我是通过sdkman来管理 GraalVM 的,建议你也这样做。
安装 sdkman
使用 sdkman 查看可用的 GraalVM
结果如图:
找一个最新版本安装,此处是 20.3.0.r11-grl
这里提一句,执行完安装后,sdkman 会询问你是否把刚安装的环境作为默认环境,我建议你选择否,这样就不会对你电脑之前安装好的 Java、Node 环境造成影响。当您想使用 20.3.0.r11-grl 的时候,只需要执行 sdk use java 20.3.0.r11-grl,它只会影响当前终端上下文的环境。
基本的环境准备好后,你可以试试node --version
,应该可以看到 v12.18.4 的输出,而如果你执行 which node,应当会看到类似下面的内容:
有意思吧?GraalVM 居然自带了一个 node 命令,一个不需要你安装https://nodejs.org就可以使用的 node🤓。
安装完 GraalVM,还需要安装多语言扩展包
js/node 是一等公民,不需要特意安装,这个在上一节已经见识了😋
经过漫长的等待,GraalVM 及多语言环境就算安装完成,终于可以进入代码环节了。我们直接采用官方 GitHub 中的一个例子——polyglot-javascript-java-r,一个演示了 JavaScript、R、Java 混合使用的精彩 demo。
你只需要准备两个文件就可以了,先来看看 package.json,内容非常简单:
重头戏都在 server.js,内容如下:
除了常规的 node 和 express 使用外,比较有意思的就是对 Java 和 R 的访问了。咱们一个个来看。
后续就可以在 JavaScript 无缝使用 Java 中的功能,比如静态方法 valueOf、pow、add。一通操作后,我们获得了一个 10 的 100 次方的超大整数,并让其和 43 相加。
另一段比较长的 Polyglot.eval 就是本次 demo 的核心了, 其实也是 R 语言的专属技能。关于 R 语言的使用,已经超出了本文的讨论范围,有兴趣的小伙伴可以查看官方文档。
让我们来看看结果吧,先执行 npm install 把依赖安装好,然后你要用 GraalVM 的 node 命令启动项目,请一定注意,不是用你曾经熟悉的那个 node 哦。执行 node --jvm --polyglot server.js,然后你会看到如下输出:
赶紧打开浏览器访问一下http://127.0.0.1:3000看看效果吧:
非常精彩,你不费吹灰之力就在 html 中构建了一个基于 svg 的三维坐标系,仿佛开启了在 html 展示数学技艺的大门——当然,如果你想进门转转的话,还得回头恶补一下数学和 R 语言,至少我是这么打算的😉。
现在你理解多语言的程序环境是一件多么激动人心事了吧。这里没有口水战,有的只是百家争鸣,取长补短,共建繁荣。所以我才说 Oracle 的野心真的很大,通过 GraalVM 拿出一个多语言支持的环境,并且性能还更强,还可以预编译成二进制文件,直接分发高效运行。如果真有这样的东西,谁不喜欢呢。
三、编译二进制香不香下面终于到最后一个环节了,把程序打包成二进制程序。按照官网的指点,首先我们要安装 native-image 来解锁编译二进制文件的技能。你需要执行:
gu install native-image 安装完 native-image 后,当我满心欢喜的执行 native-image --language:js server.js 试图把上面演示的 server.js 编译成二进制的时候,居然报错了:
去官网查阅了 native-image 的文档才知道,native-image 只支持 JVM-based 的语言,比如 Java、Scala、Clojure、Kotlin 这些 JVM 的亲缘语言。
没办法,那我们还是拿 Java 开刀吧,这里我准备了一个简单的 Java 类:
可以看到中间我用 GraalVM 提供的工具执行了一小段 JavaScript 脚本,通过 JavaScript 中的 eval 函数计算了 1+1。eval 这种开挂的函数在 JavaScript 这种动态语言里挺常见的,但是在 Java 这种静态语言就特别少见,也制约了好多编程场景,我个人感觉 GraalVM 能通过这种非主流的方式给 Java 带来 eval 的特性还挺香的。下面我们来把这个 Class 打包成二进制文件:
第一步,编译.java 为.class:
第二步,有了.class 文件后我们可以验证一下代码:
这时控制台打印了 2,看起来一切正常。
第三步,用 native-image 把.class 编译可执行文件(也就是二进制文件)。
由于代码里我们用到了 JavaScript 相关内容,所以编译的时候需要提供参数--language:js。
经过一个漫长的等待(我这边是 3 分多钟),还有恐怖的内存消耗(被 native-image 进程消耗掉了 6 个多 G),我们终于得到了如图的执行结果:
当然还有我们想要的可执行文件 polyglotjavaJS,一个惊人的 97M 的文件。
好在这个文件执行起来还不赖,直接运行./polyglotjavajs 就可以看到我们想要的结果了。如果在执行的命令前面加上 time,我们就能和 JVM 解释执行的版本做个对比了:
可以看到,二进制版本确实有着更快的执行速度和更低的 CPU 使用率,跟 JVM 版本对比优势还是比较明显的。就是 97M 的文件大小,我觉得有点大了,不利于分发。下面尝试把 JavaScript 的相关内容移除,只实现最简单的逻辑:
然后我们再次执行 javac 和 native-image PolyglotJavaJS,又得到了一组新的编译日志:
可喜可贺,编译过程对内存和时间的消耗都只有之前的三分之一。再来看下编译后的文件大小:
只有 7.7M 了,瞬间觉得香了,有木有。同样的,我们再把二进制和 JVM 版的执行速度对比一下:
二进制版本依然保持了不俗的优势。
这么看来 GraalVM 打包二进制可执行文件的功能,在 JVM 系语言上还是有一定优势的。除了打包过程中耗时比较长以及对机器的 CPU、内存有不小的占用,二进制程序执行速度还是挺快的,明显感觉比解释型执行的语言要快。打包出来的文件 Size,确实不小,与主流的硬编译语言 C、C++、Go、Rust 比差太远了,我随便测试了一个 Rust 的 Hello World 程序,编译后的可执行文件才 370K。这方面的差距,我觉得在很长一段时间 GraalVM 是不可能追赶上了。好在 5G 时代带宽可能不是个大问题,磁盘也越来越白菜价了,可执行文件的大小应该不是我们要考量的主要因素。
总结
GraalVM 在整个 JVM 领域无疑是个异类,有众多激进的特性。并且由 Oracle 做背书,同时提供社区版和商业版,可持续性不需担心,未来的发展肯定是要越来越好的。但是 Java 作为一个有年代感的语言,早也不复舞台 C 位的荣光。最近几年 JVM 系的好东西涌现了不少,无奈国内接受度真的不高,这可能是我辈 JVM 系程序员最尴尬的境地了。
版权声明: 本文为 InfoQ 作者【李浩宇/Alex】的原创文章。
原文链接:【http://xie.infoq.cn/article/cecd83458031c90c05910cc44】。文章转载请联系作者。
评论