1Java语言在云原生时代的困境经过多年的演进,Java语言的功能和性能都在不断发展和完善,如即时编译器、垃圾收集器等。系统可以体现Java语言的优秀,但要想享受这些功能带来的改进,还需要一段时间的运行才能达到最佳性能。一般来说,Java是为大规模、长期使用的服务器端应用而设计的。云原生时代,Java语言一次编译到处运行的优势不复存在。理论上,使用容器化技术,所有语言都可以部署在云端,而离不开JVM的Java应用往往会面临比应用本身更大的JDK内存占用。困境;Java的动态加载和卸载特性也使得构建的应用镜像中有一半以上是无用代码和依赖,使得Java应用占用大量内存。但启动时间长,性能达到峰值时间长,使其在Serverless等场景下无法与Go、Node.js等快速语言抗衡。Java应用运行生命周期示意图2GraalVM面对Java在云原生时代的不适,GraalVM或许是最好的解药。GraalVM是甲骨文实验室推出的基于Java开发的开源高性能多语言运行时平台。它可以运行在传统的OpenJDK上,也可以通过AOT(Ahead-Of-Time)编译成可执行文件独立运行,甚至可以集成到数据库中运行。此外,它还消除了编程语言之间的界限,支持通过即时编译技术将混合不同编程语言的代码编译成相同的二进制代码,从而实现不同语言之间的无缝通信。接缝切换。本文主要从三个方面简要介绍GraalVM能够给我们带来的改变:1)基于Java的GraalCompiler的出现对于学习和研究虚拟机代码编译技术具有不可估量的价值。与C++端编译器编写的复杂服务相比,无论是对编译器的优化还是学习成本都大大降低。2)静态编译框架SubstrateVM框架为Java提供了在云原生时代与其他语言竞争的可能,大大降低了Java应用程序占用的内存,可以将启动速度加快数十倍。3)以Truffle、Sulong为代表的中级语言翻译人员。开发者可以使用Truffle提供的API快速实现一个Java语言解释器,从而实现在JVM平台上运行其他语言的效果,将Java推向世界。越来越多的想象力的可能性来了。GraalVM多语言支持3GraalVM整体结构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├──编译器├──docs├──espresso├──graal-common.json├──java-benchmarks├──regex├──repo-configuration.libsonnet├──sdk├──substratevm├──sulong├──tools├──truffle├──vm└──wasm3.1CompilerCompiler子项目的全称是GraalVM编译器,是用Java语言编写的Java编译器。编译效率高,输出质量高,同时支持提前编译(AOT)和即时编译(JIT),支持HotSpot等不同虚拟机的编译器。它使用了与C2相同的中间表示(Seaof??NodesIR),在后端优化方面直接继承了HotSpot的服务端编译器的大量优质优化技术。平台。GraalCompiler是GraalVM和HotSpotVM(从JDK10开始)共同拥有的服务器端即时编译器,是C2编译器的未来替代品。为了将Java虚拟机与编译器解耦,ORACLE引入了Java-LevelJVMCompilerInterface(JVMCI)Jep243:将编译器从虚拟机中抽取出来,通过接口与虚拟机通信(https://openjdk.java.net/jeps/243)具体来说,即时编译器与Java虚拟机的交互可以分为以下三个方面。响应编译请求;获取编译所需的元数据(如类、方法、字段)和反映程序执行状态的profile;将生成的二进制代码部署到代码缓存(codecache)中。oracle提供的编译时差示例3.2SubstrateVMSubstrateVM提供了一个将Java程序静态编译成native代码的编译工具链,包括编译框架、静态分析工具、C++支持框架和运行时支持。在程序运行之前将字节码转换为机器码。优点:静态可达性分析从指定编译入口开始,有效控制编译范围,解决代码扩展问题;实现各种运行时优化如:传统的java类在第一次使用时进行初始化,然后每次调用时检查是否初始化过。GraalVM将其优化为在编译时初始化;实时编译无需消耗CPU资源,程序从一开始就可以达到理想的性能;缺点:静态分析是资源密集型计算,会消耗大量的CPU、内存和时间;静态分析对反射、JNI、动态代理的分析能力非常有限。目前GraalVM只能通过额外配置来解决;Java序列化还有一些违反封闭假设的动态特性:反射、JNI、动态类加载,目前GraalVM也需要通过额外的配置来解决,无法处理所有的序列化,比如Lambda对象的序列化,以及性能是JDK的一半;启动时间对比和内存使用对比3.3Truffle我们知道一般编译器分为前端和后端,前端负责词法分析、语法分析、类型检查和中间代码生成。后端负责编译优化和目标代码生成。比较tricky的方法是将新语言编译成已知的语言,比如Scala、Kotlin,可以编译成Java字节码,这样就可以直接享受JVM的JIT、GC等优化。目标编译语言。相比之下,解释型语言如JavaScript、Ruby、R、Python等,依赖解释型执行器进行解析和执行。为了让这类解释型语言能够更高效地执行,开发者通常需要开发虚拟机。并实现垃圾回收、即时编译等组件,使语言可以在虚拟机中执行,比如谷歌的V8引擎。如果这些语言也能跑在JVM上,复用JVM的各种优化方案,就会减少很多重新造轮子的消耗。这也是Truffle项目的目标。Truffle是一个用Java编写的解释器实现框架。提供解释器开发框架接口,可以帮助开发者使用Java快速开发自己感兴趣的语言的语言解释器。目前已经实现并维护了JavaScript、Ruby、R、Python等语言。只需要基于Truffle实现相关语言的词法分析器和语法分析器,以及语法分析生成的抽象语法树(AST)的解释执行器,即可运行在任何Java虚拟机上,享受各种JVM提供的操作。时间优化。GraalVM多语言运行时性能加速比3.3.1PartialEvaluationTruffle的实现原理是基于PartialEvaluation的概念:假设程序prog是将输入转化为输出。Istatic是静态数据,是编译时已知的常量,Idynamic在编译时是未知的。数据,程序可以等价于:新程序prog_是prog的特化,应该比原程序执行得更有效率。这个从prog到prog_的转换过程称为部分求值。我们可以把Truffle预加载的解释执行器看成prog,用Truffle语言写的程序看成Istatic,通过PartialEvaluation把prog转换成prog*。下面以Oracle官方的例子来说明。下面的程序实现了读取参数和添加参数的操作。需要读取并添加三个参数:本程序分析生成的AST为sample=newAdd(newAdd(newArg(0),newArg(1)),newArg(2));经过PartialEvaluator的连续方法内联,最终会变成如下代码:3.3.2Node重写Node重写是Truffle的另一个关键优化。在动态语言中,许多变量的类型只能在运行时确定。以“加法”为例,符号+可以表示整数加法,也可以表示浮点数加法。Truffle的语言解释器会收集每个AST节点所代表的操作类型(profile),并在编译时对收集到的profile进行优化,例如:如果收集到的profile显示这是一个整数加法运算,Truffle会在just-实时编译,将“+”视为整数加法。当然,这种优化也是有失误的。例如,上述加法运算可以是整数加法,也可以是字符串加法。如果此时AST树发生了变形,那么我们就不得不丢弃编译后的机器码,回退到AST的解释和执行上。这种基于配置文件的优化背后的核心是基于假设的推测优化和假设失败时的反优化。即时编译后,如果在运行过??程中发现AST节点的实际类型与假定类型不同,Truffle会主动调用Graal编译器提供的反优化API,返回解释状态,执行AST节点,重新收集AST节点类型信息。之后,Truffle将再次使用Graal编译器进行新一轮的即时编译。据统计,在JavaScript方法和Ruby方法中,80%的方法调用5次后稳定,90%的方法调用7次后稳定,99%的方法调用19次后稳定。3.4SulongSulong子项目是GraalVM为LLVM的中间语言bitcode提供的高科技运行时工具,是基于Truffle框架的bitcode解释器。速龙为所有可以编译为LLVMbitcode的语言(如C、C++等)提供了在JVM中执行的解决方案。4参考林子怡《GraalVM与静态编译》;周志明《深入理解Java虚拟机》;JavaDeveloper’sIntroductiontoGraalVM:-ZhengYudiTruffle/Graal:FromInterpreterstoOptimizingCompilersviaPartialEvaluation:-卡内基梅隆大学作者:王子豪
