初步探索GraalVM——云原生时代JVM黑科技
1 云原生时代Java语言的困境
经过多年的演进,Java语言的功能和性能都在不断的发展和提高,诸如即时编译器、垃圾回收器等系统都能体现Java语言的优秀,但是想要享受这些功能带来的提升都需要一段时间的运行来达到最佳性能,总的来说Java是面向大规模、长时间使用的服务端应用而设计的。
云原生时代,Java语言一次编译到处运行的优势不复存在,理论上使用容器化技术,所有语言都能部署上云,而无法脱离JVM的Java应用往往要面对JDK内存占用比应用本身还大的窘境;Java动态加载、卸载的特性也使得构建的应用镜像中有一半以上的无用代码和依赖这些都使得Java应用占用内存相当多。而启动时间长,性能达到峰值的时间长使得在Serverless等场景下无法与Go、Node.js等快速语言竞争。
Java应用程序的运行生命周期示意图
2 GraalVM
面对云原生时代Java的不适,GraalVM或许是最好的解药。GraalVM是Oracle实验室推出的基于Java开发的开源高性能多语言运行时平台,它既可以在传统的 OpenJDK 上运行,也可以通过 AOT(Ahead-Of-Time)编译成可执行文件单独运行,甚至可以集成至数据库中运行。除此之外,它还移除了编程语言之间的边界,并且支持通过即时编译技术,将混杂了不同的编程语言的代码编译到同一段二进制码之中,从而实现不同语言之间的无缝切换。
本文主要简单从三个方面介绍GraalVM可以为我们带来的改变:
1)基于Java的Graal Compiler的出现对学习和研究虚拟机代码编译技术有着不可估量的价值,相比C++编写的复杂无比的服务端编译器,不管是对编译器的优化还是学习的成本都大大的降低。
2)静态编译框架Substrate VM框架,为Java在云原生时代提供了与其他语言竞争的可能,大大的减少了Java应用占用内存,并且可以加快启动速度几十倍。
3)以Truffle和Sulong为代表的中间语言解释器,开发者可以使用Truffle提供的API快速用Java实现一种语言的解释器,从而实现了在JVM平台上运行其他语言的效果,为Java世界带来了更多更有想象力的可能性。
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)
具体来说,即时编译器与 Java 虚拟机的交互可以分为如下三个方面。
- 响应编译请求;
- 获取编译所需的元数据(如类、方法、字段)和反映程序执行状态的 profile;
- 将生成的二进制码部署至代码缓存(code cache)里。
oracle提供的编译时间差异示例
3.2 Substrate VM
Substrate VM提供了将Java程序静态编译为本地代码的编译工具链,包括了编译框架、静态分析工具、C++支持框架及运行时支持等。在程序运行前便将字节码转换为机器码
优点:
- 从指定的编译入口开始静态可达性分析,有效的控制了编译范围,解决了代码膨胀的问题;
- 实现了多种运行时优化例如:传统的java类是在第一次被用到时初始化的,之后每次调用时还要再检查是否初始化过,GraalVM将其优化为在编译时初始化;
- 无需在运行过程中耗费CPU资源来进行即时编译,而程序也能在启动一开始就达到理想的性能;
缺点:
- 静态分析是资源密集型计算,需要消耗大量CPU、内存和时间;
- 静态分析对反射、JNI、动态代理的分析能力非常有限,目前GraalVM只能通过额外配置的方式加以解决;
- Java序列化也有多项违反封闭性假设的动态特性:反射,JNI,动态类载入,目前GraalVM也需要通过额外配置解决,且不能处理所有序列化,例如Lambda对象的序列化,而且性能是JDK的一半;
启动时长对比
占用内存对比
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提供的各项运行时优化。
GraalVM多语言运行时性能加速比
3.3.1 Partial Evaluation
Truffle的实现原理基于Partial Evaluation这一概念:假设程序prog为将输入转为输出
其中Istatic为静态数据,在编译时已知常量,Idynamic为编译时未知数据,则可以将程序等价为:
新程序prog为prog的特化,他应该会比原程序更高效的执行,这个从prog转换到prog的过程便称为Partial Evaluation。我们可以将Truffle预压的解释执行器当成prog,将某段由Truffle语言写的程序当做Istatic,并通过Partial Evaluation将prog转换到prog*。
下面引用一个Oracle官方的例子来讲解,以下程序实现了读取参数以及参数相加的操作,需要实现读取三个参数相加:
这段程序解析生成的AST为
sample = new Add(new Add(new Arg(0), new Arg(1)), new Arg(2));
经过Partial Evaluator 的不断进行方法内联最终会变成下述代码:
3.3.2 节点重写
节点重写是Truffle的另一项关键优化。
在动态语言中许多变量的类型是在运行时才能确定的,以“加法”举例,符号+即可以表示整型相加也可以表示浮点型相加。Truffle的语言解释器会收集每个AST节点所代表的操作类型(profile),并且在编译时做出针对所收集到的profile进行优化,如:若收集到的profile显示这是一个整型加法操作,Truffle会在即时编译时将AST进行变形,将“+”视为整型加法。
当然,这种优化也会有错误的时候,比如上述加法操作既有可能是整数加法也可能是字符串加法,此时若AST树已变形,那么我们只好丢弃编译后的机器代码,回退到AST解释执行。这种基于类型 profile 的优化,背后的核心就是基于假设的投机性优化,以及在假设失败时的去优化。
在即时编译过后,如果运行过程中发现 AST 节点的实际类型和所假设的类型不同,Truffle 会主动调用 Graal 编译器提供的去优化 API,返回至解释执行 AST 节点的状态,并且重新收集 AST 节点的类型信息。之后,Truffle 会再次利用 Graal 编译器进行新一轮的即时编译。
据统计,在 JavaScript 方法和 Ruby 方法中,80% 会在 5 次方法调用后稳定下来,90% 会在 7 次调用后稳定下来,99%会在 19 次方法调用之后稳定下来。
3.4 Sulong
Sulong子项目是GraalVM为LLVM的中间语言bitcode提供的高新更运行时工具,是基于Truffle框架实现的bitcode解释器。Sulong为所有可以编译到LLVM bitcode的语言(如C,C++等)提供了在JVM中执行的解决方案。
4 参考
- 林子熠 《GraalVM与静态编译》;
- 周志明《深入理解Java虚拟机》;
- Java Developer’s Introduction to GraalVM:-郑雨迪
- Truffle/Graal:From Interpreters toOptimizing Compilers via Partial Evaluation:-Carnegie Mellon University
作者:王子豪
初步探索GraalVM——云原生时代JVM黑科技的更多相关文章
- 🏆【JVM深层系列】「云原生时代的Java虚拟机」针对于GraalVM的技术知识脉络的重塑和探究
GraalVM 背景 新.旧编程语言的兴起躁动,说明必然有其需求动力所在,譬如互联网之于JavaScript.人工智能之于Python,微服务风潮之于Golang等等.大家都清楚不太可能有哪门语言能在 ...
- 进击的 Java ,云原生时代的蜕变
作者| 易立 阿里云资深技术专家 导读:云原生时代的来临,与Java 开发者到底有什么联系?有人说,云原生压根不是为了 Java 存在的.然而,本文的作者却认为云原生时代,Java 依然可以胜任&qu ...
- [转帖]从 SOA 到微服务,企业分布式应用架构在云原生时代如何重塑?
从 SOA 到微服务,企业分布式应用架构在云原生时代如何重塑? 2019-10-08 10:26:28 阿里云云栖社区 阅读数 54 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权 ...
- .NET 在云原生时代的蜕变,让我在云时代脱颖而出
.NET 生态系统是一个不断变化的生态圈,我相信它正在朝着一个伟大的方向发展.有了开源和跨平台这两个关键优先事项,我们就可以放心了.云原生对应用运行时的不同需求,说明一个.NET Core 在云原生时 ...
- 【转】.NET 在云原生时代的蜕变,让我在云时代脱颖而出
原创:张善友 原文:https://www.cnblogs.com/shanyou/p/12198741.html .NET 生态系统是一个不断变化的生态圈,我相信它正在朝着一个伟大的方向发展.有了开 ...
- 云原生时代,Java的危与机(周志明)
说明 本篇文章是转载自周志明老师的文章,链接地址:https://www.infoq.cn/article/RQfWw2R2ZpYQiOlc1WBE 今天,25 岁的 Java 仍然是最具有统治力的编 ...
- 云原生时代的Java
原文链接(作者:周志明):https://time.geekbang.org/column/article/321185 公开课链接:https://time.geekbang.org/opencou ...
- 云原生时代的DevOps平台设计之道
开发人员与运维人员是 IT 领域很重要的两大人群,他们都会参与到各种业务系统的建设过程中去.DevOps 是近年间火爆起来的一种新理念,这种理念被很多人错误的解读为"由开发人员(Dev)学习 ...
- 阿里云弹性容器实例产品 ECI ——云原生时代的基础设施
阿里云弹性容器实例产品 ECI ——云原生时代的基础设施 1. 什么是 ECI 弹性容器实例 ECI (Elastic Container Instance) 是阿里云在云原生时代为用户提供的基础计算 ...
- 进击的.NET 在云原生时代的蜕变
你一定看过这篇文章 <进击的 Java ,云原生时代的蜕变>, 本篇文章的灵感来自于这篇文章.明天就将正式发布.NET Core 3.0, 所以写下这篇文章让大家全面认识.NET Cor ...
随机推荐
- Vmware虚拟主机启动卡死问题解决
记录一次虚拟主机开机卡死,黑屏,无法操作的问题 一.问题现象 1.在vmware上新建数台主机后,第一次启动都正常,部分主机出现关机后再开机(或直接重启)卡死的情况: 2.在vmware上右键菜单栏均 ...
- Yarn上运行spark-shell和spark-sql命令行
spark-shell On Yarn spark-shell \ --master yarn-client \ --executor-memory 1G \ --num-executors 10 s ...
- etcd v3版本生产级集群搭建以及实现一键启动脚本
本专栏的上一篇文章写了<长篇图解etcd核心应用场景及编码实战>,本文继续.后续计划章节内容如下: <长篇图解etcd核心应用场景及编码实战> <搭建高可用etcd集群& ...
- [Python]-sklearn模块-机器学习Python入门《Python机器学习手册》-02-加载数据:加载数据集
<Python机器学习手册--从数据预处理到深度学习> 这本书类似于工具书或者字典,对于python具体代码的调用和使用场景写的很清楚,感觉虽然是工具书,但是对照着做一遍应该可以对机器学习 ...
- flutter系列之:flutter中常用的ListView layout详解
目录 简介 ListView详解 ListView中的特有属性 ListView的构造函数 ListView的使用 总结 简介 ListView是包含多个child组件的widget,在ListVie ...
- win7通过netsh命令禁用、启用本地连接 定时关闭开启网络连接
1) 先检查网络接口名称 C:\Windows\system32>netsh interface show interface 管理员状态 状态 类型 接口名称 ---------------- ...
- C语言下for循环的一点技巧总结
for循环是普遍应用与各种计算机语言的一种循环方式. 一般情况下, for循环规则:for(条件一:条件二:条件三) 条件一为满足条件,也就是条件一为1时,进入这个for循环.条件二为循环条件,也就是 ...
- PHP全栈开发(八):CSS Ⅲ background
设置背景颜色: div { background-color:#b0c4de; } 不仅可以给body标签设置背景颜色,还能给p,div,h标签设置背景颜色 设置背景图片: body { backgr ...
- POJ3260 The Fewest Coins(混合背包)
支付对应的是多重背包问题,找零对应完全背包问题. 难点在于找上限T+maxv*maxv,可以用鸽笼原理证明,实在想不到就开一个尽量大的数组. 1 #include <map> 2 #inc ...
- 洛谷P1496 火烧赤壁 (模拟/离散化+差分)
分析可知:将起点和终点按照从小到大的顺序排序,对答案不会产生影响 所以此时我们得到一种模拟做法: 1 #include<bits/stdc++.h> 2 using namespace s ...