初探GraalVM:云原生时代JVM黑科技

来源 | OSCHINA 社区

作者 | 京东云开发者-王子豪

原文链接:https://my.oschina.net/u/4090830/blog/5594298

1 云原生时代 Java 语言的困境

经过多年的演进,Java 语言的功能和性能都在不断的发展和提高,诸如即时编译器、垃圾回收器等系统都能体现 Java 语言的优秀,但是想要享受这些功能带来的提升都需要一段时间的运行来达到最佳性能,总的来说 Java 是面向大规模、长时间使用的服务端应用而设计的。

云原生时代,Java 语言一次编译到处运行的优势不复存在,理论上使用容器化技术,所有语言都能部署上云,而无法脱离 JVM 的 Java 应用往往要面对 JDK 内存占用比应用本身还大的窘境;Java 动态加载、卸载的特性也使得构建的应用镜像中有一半以上的无用代码和依赖这些都使得 Java 应用占用内存相当多。而启动时间长,性能达到峰值的时间长使得在 Serverless 等场景下无法与 Go、Node.js 等快速语言竞争。

ef6777af5c32ca7905c5ba786306d92b.png

Java 应用程序的运行生命周期示意图

2 GraalVM

面对云原生时代 Java 的不适,GraalVM 或许是最好的解药。GraalVM 是 Oracle 实验室推出的基于 Java 开发的开源高性能多语言运行时平台,它既可以在传统的 OpenJDK 上运行,也可以通过 AOT(Ahead-Of-Time)编译成可执行文件单独运行,甚至可以集成至数据库中运行。除此之外,它还移除了编程语言之间的边界,并且支持通过即时编译技术,将混杂了不同的编程语言的代码编译到同一段二进制码之中,从而实现不同语言之间的无缝切换。

6c1f69658518cd2e93aa282d754bd166.png

本文主要简单从三个方面介绍 GraalVM 可以为我们带来的改变:

1) 基于 Java 的 Graal Compiler 的出现对学习和研究虚拟机代码编译技术有着不可估量的价值,相比 C++ 编写的复杂无比的服务端编译器,不管是对编译器的优化还是学习的成本都大大的降低。
2) 静态编译框架 Substrate VM 框架,为 Java 在云原生时代提供了与其他语言竞争的可能,大大的减少了 Java 应用占用内存,并且可以加快启动速度几十倍。

bcb76b61581c5a6c68ceb37bcb3a5904.png

3) 以 Truffle 和 Sulong 为代表的中间语言解释器,开发者可以使用 Truffle 提供的 API 快速用 Java 实现一种语言的解释器,从而实现了在 JVM 平台上运行其他语言的效果,为 Java 世界带来了更多更有想象力的可能性。

8889a203d85ac94d708e05713153b229.png
GraalVM 多语言支持

本周福利:图灵奖是怎么来的?

3 GraalVM 整体结构

graal
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── THIRD_PARTY_LICENSE.txt
├── bench-common.libsonnet
├── ci-resources.libsonnet
├── ci.hocon
├── ci.jsonnet
├── ci_includes
├── common-utils.libsonnet
├── common.hocon
├── common.json
├── common.jsonnet
├── compiler
├── docs
├── espresso
├── graal-common.json
├── java-benchmarks
├── regex
├── repo-configuration.libsonnet
├── sdk
├── substratevm
├── sulong
├── tools
├── truffle
├── vm
└── wasm

3.1 Compiler

Compiler 子项目全称 GraalVM 编译器,是用 Java 语言编写的 Java 编译器。高编译效率、高输出质量、同时支持提前编译(AOT)和即时编译(JIT)、同时支持应用于包括 HotSpot 在内的不同虚拟机的编译器。

与 C2 采用一样的中间表示形式(Sea of Nodes IR),后端优化上直接继承了大量来自于 HotSpot 的服务端编译器的高质量优化技术,是现在高校、研究院和企业编译研究实践的主要平台。

Graal Compiler 是 GraalVM 与 HotSpotVM(从 JDK10 起)共同拥有的服务端即时编译器,是 C2 编译器未来的替代者。为了让 Java 虚拟机与编译器解耦,ORACLE 引入了 Java-Level JVM Compiler Interface (JVMCI) Jep 243 :把编译器从虚拟机中抽离出来,并且可以通过接口与虚拟机交流(https://openjdk.java.net/jeps/243)

8472435b78f9ea0e8872786e44522a0f.png

具体来说,即时编译器与 Java 虚拟机的交互可以分为如下三个方面。

  1. 响应编译请求;

  2. 获取编译所需的元数据(如类、方法、字段)和反映程序执行状态的 profile;

  3. 将生成的二进制码部署至代码缓存(code cache)里。

fd5fe78936a4a6601e8f8de9d977acca.png

0f00fafaa165b75f601f5bd35549a2c7.png

77404e622f37ea3d2569056c9b156360.png
oracle 提供的编译时间差异示例

3.2 Substrate VM

Substrate VM 提供了将 Java 程序静态编译为本地代码的编译工具链,包括了编译框架、静态分析工具、C++ 支持框架及运行时支持等。在程序运行前便将字节码转换为机器码

优点:

  1. 从指定的编译入口开始静态可达性分析,有效的控制了编译范围,解决了代码膨胀的问题;

  2. 实现了多种运行时优化例如:传统的 java 类是在第一次被用到时初始化的,之后每次调用时还要再检查是否初始化过,GraalVM 将其优化为在编译时初始化;

  3. 无需在运行过程中耗费 CPU 资源来进行即时编译,而程序也能在启动一开始就达到理想的性能;

缺点:

  1. 静态分析是资源密集型计算,需要消耗大量 CPU、内存和时间;

  2. 静态分析对反射、JNI、动态代理的分析能力非常有限,目前 GraalVM 只能通过额外配置的方式加以解决;

  3. Java 序列化也有多项违反封闭性假设的动态特性:反射,JNI,动态类载入,目前 GraalVM 也需要通过额外配置解决,且不能处理所有序列化,例如 Lambda 对象的序列化,而且性能是 JDK 的一半;

cf61dd475ba8c66c80ea342538db2af3.png

e8369b6a73bc26dac142e6c6b765d434.png

启动时长对比

2f2d4345bddb5c58f085e55d7e7f88e0.png

占用内存对比

3.3 Truffle

我们知道一般编译器分为前端和后端,前端负责词法分析、语法分析、类型检查和中间代码生成,后端负责编译优化和目标代码生成。一种比较取巧的做法是将新语言编译成某种已知语言,如 Scala、Kotlin 可以编译成 Java 字节码,这样就可以直接享用 JVM 的 JIT、GC 等各项优化,这种做法都是针对的编译型语言。与之相对的,如 JavaScript、Ruby、R、Python 等解释型语言,它们依赖于解释执行器进行解析并执行,为了让这类解释型语言能够更高效的执行,开发人员通常需要开发虚拟机,并实现垃圾回收,即时编译等组件,让该语言在虚拟机中执行,如 Google 的 V8 引擎。如果能让这些语言也可以在 JVM 上运行并复用 JVM 的各种优化方案,将会减少许多重复造轮子的消耗。这也是 Truffle 项目的目标。

Truffle 是一个用 Java 编写的解释器实现框架。它提供了解释器的开发框架接口,可以帮助开发人员用 Java 为自己感兴趣的语言快速开发处语言解释器,目前已经实现并维护了 JavaScript、Ruby、R、Python 等语言。

只需基于 Truffle 实现相关语言的词法分析器、语法分析器及针对语法分析所生成的抽象语法树(AST)的解释执行器,便可以运行在任何 Java 虚拟机上,享用 JVM 提供的各项运行时优化。

8d89d78644c91adb674df878556d6524.png

GraalVM 多语言运行时性能加速比

3.3.1 Partial Evaluation

Truffle 的实现原理基于 Partial Evaluation 这一概念:假设程序 prog 为将输入转为输出

c353a2b4075ad2ebfcc54b8edd2c7e37.png

其中 Istatic 为静态数据,在编译时已知常量,Idynamic 为编译时未知数据,则可以将程序等价为:

b64fd9546cd022fdeff81618f3a927a3.png

新程序 prog 为 prog 的特化,他应该会比原程序更高效的执行,这个从 prog 转换到 prog 的过程便称为 Partial Evaluation。我们可以将 Truffle 预压的解释执行器当成 prog,将某段由 Truffle 语言写的程序当做 Istatic,并通过 Partial Evaluation 将 prog 转换到 prog*。

下面引用一个 Oracle 官方的例子来讲解,以下程序实现了读取参数以及参数相加的操作,需要实现读取三个参数相加:

98fac7a9e72ca2bbeab3f7a2426b3c18.png

这段程序解析生成的 AST 为

sample = new Add(new Add(new Arg(0), new Arg(1)), new Arg(2));

cff389267763daf6c726ba09f7c54055.png

经过 Partial Evaluator 的不断进行方法内联最终会变成下述代码:

414504fc79f77106615b620507dd8b28.png

3.3.2 节点重写

节点重写是 Truffle 的另一项关键优化。

在动态语言中许多变量的类型是在运行时才能确定的,以 “加法” 举例,符号 + 即可以表示整型相加也可以表示浮点型相加。Truffle 的语言解释器会收集每个 AST 节点所代表的操作类型(profile),并且在编译时做出针对所收集到的 profile 进行优化,如:若收集到的 profile 显示这是一个整型加法操作,Truffle 会在即时编译时将 AST 进行变形,将 “+” 视为整型加法。

当然,这种优化也会有错误的时候,比如上述加法操作既有可能是整数加法也可能是字符串加法,此时若 AST 树已变形,那么我们只好丢弃编译后的机器代码,回退到 AST 解释执行。这种基于类型 profile 的优化,背后的核心就是基于假设的投机性优化,以及在假设失败时的去优化。

5fbbc95a12c2e80cd254c5b889c3ab4a.png

在即时编译过后,如果运行过程中发现 AST 节点的实际类型和所假设的类型不同,Truffle 会主动调用 Graal 编译器提供的去优化 API,返回至解释执行 AST 节点的状态,并且重新收集 AST 节点的类型信息。之后,Truffle 会再次利用 Graal 编译器进行新一轮的即时编译。

据统计,在 JavaScript 方法和 Ruby 方法中,80% 会在 5 次方法调用后稳定下来,90% 会在 7 次调用后稳定下来,99% 会在 19 次方法调用之后稳定下来。

c29e1f02640f63a7c460e93db67c868f.png

3.4 Sulong

Sulong 子项目是 GraalVM 为 LLVM 的中间语言 bitcode 提供的高新更运行时工具,是基于 Truffle 框架实现的 bitcode 解释器。Sulong 为所有可以编译到 LLVM bitcode 的语言(如 C,C++ 等)提供了在 JVM 中执行的解决方案。

6f9805e7437fc20869fef442d46292fb.png

4 参考

  • 林子熠 《GraalVM 与静态编译》;

  • 周志明《深入理解 Java 虚拟机》;

  • Java Developer’s Introduction to GraalVM:- 郑雨迪

  • Truffle/Graal:From Interpreters toOptimizing Compilers via Partial Evaluation:-Carnegie Mellon University

推荐阅读:
世界的真实格局分析,地球人类社会底层运行原理
不是你需要中台,而是一名合格的架构师(附各大厂中台建设PPT)

企业IT技术架构规划方案

论数字化转型——转什么,如何转?

华为干部与人才发展手册(附PPT)

企业10大管理流程图,数字化转型从业者必备!

【中台实践】华为大数据中台架构分享.pdf

华为的数字化转型方法论

华为如何实施数字化转型(附PPT)

超详细280页Docker实战文档!开放下载

华为大数据解决方案(PPT)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值