![GraalVM与Java静态编译:原理与应用](https://wfqqreader-1252317822.image.myqcloud.com/cover/558/41992558/b_41992558.jpg)
2.2 华为方舟编译器
华为编译器实验室在Java静态编译方面经过多年深耕,于2019年4月推出方舟编译器,并于2019年8月底正式开源。方舟编译器是用C++语言编写的Java静态编译框架,包括编译器、工具链和支持静态编译后的代码运行的轻量级运行时环境三大部分。方舟编程平台体系如图2-5所示,方舟静态编译器是其中的核心组件。该平台旨在打造多种编程语言、多种芯片平台的联合编译/运行的统一平台,支持将多种语言的前端统一到方舟的中间语言,由其强大的编译器对中间语言进行编译优化,最后输出高质量的本地代码。
![034-01](https://epubservercos.yuewen.com/0AE46C/22010258901905506/epubprivate/OEBPS/Images/034-01.jpg?sign=1739649130-Zk8VkDOvIdimHFigRnOTNqRTdgfMOEf5-0-0ca3ce4613f6c07ea01177338d872cdb)
图2-5 方舟编程平台体系结构图
由于华为的产业布局特点,方舟率先支持了移动端的安卓Java静态编译。安卓Java是谷歌面向移动端的定制化的JDK,其部分核心类库被改造得更适合移动场景,字节码是经过优化的基于寄存器的dex代码,运行时环境则是谷歌的ART(Android Runtime)。与这些改变相对应的是,方舟编译器接受的输入是dex文件,输出是AArch64平台的本地代码,运行时行为与ART保持一致。
方舟的核心是名为Maple IR[1]的中间语言。方舟的编译行为可以按照与Maple IR的关系划分为前端、中端、后端。前端是指将输入的其他语言编译为Maple IR,中端是指对Maple IR进行优化,后端是指将优化后的Maple IR编译为目标平台的汇编代码。可以说方舟编译器的各项编译活动就是围绕Maple IR展开的,而该语言的特点又决定了方舟在静态编译时选择了不同于GraalVM的技术方向。
方舟在静态编译时依然遵循封闭性原则,也同样会遇到代码膨胀问题,但方舟的解决方案是模块化编译加动态链接。Maple IR将代码分为声明部分的mplt文件和实现部分的mpl文件。在这种语言设计下,方舟可以将目标程序和所需的依赖分开编译。方舟编译器的模块化编译如图2-6所示,其中App框代表应用程序,JDK框代表安卓的核心库lib-core,Lib1和Lib2框代表应用程序依赖的第三方库。在静态编译时,各个模块都可以独立编译,比如编译App时只需自身代码和JDK、Lib1以及Lib2的声明头文件即可,JDK、Lib1和Lib2则可以提前编译准备好,在运行时由方舟编译器提供的Maple Linker(链接器)将各个模块链接起来即可运行。由此,一方面将编译范围限制在了模块级别,控制了单次编译的代码量,进而降低了单次编译的资源消耗和时间开销,单个模块的更新也不会导致整个应用的重新编译(公开的API更新除外);另一方面也加大了代码的复用程度,如lib-core这种核心JDK库,只需编译一次即可被其他各个Java应用程序和第三方库复用。这种方式的缺点是无法减小最终部署时的程序大小,各个全编译的模块的总大小并不会变化。
![035-01](https://epubservercos.yuewen.com/0AE46C/22010258901905506/epubprivate/OEBPS/Images/035-01.jpg?sign=1739649130-Xc3zoZjv1K5dE9TpTVBd8O0S4SRMoGKw-0-3ca443aaace8c3e2ede7ee59a25af392)
图2-6 方舟编译器模块化编译示意图
在运行时支持方面,方舟也进行了大胆的探索。Java原有的标记与回收(Mark and Swap)垃圾回收方案需要在后台常驻垃圾回收线程,长期占用一部分系统资源,而且在回收资源时会暂停其他所有线程,给用户造成系统卡顿的感觉。考虑到以上情况,从移动端设备的实际特点和需求出发,方舟尝试了引用计数(Reference Counting,RC)的垃圾回收策略,为每个实例增加一个引用计数器,每多一次引用,引用计数器就加1,每减少一次引用则减1,当一个实例的引用数降低到0时,就被立即回收。这种方式无须额外的垃圾回收线程常驻,资源也可以被及时回收。
但是由于Java语言从设计上是反RC的,会产生RC难以处理的循环引用。虽然循环引用的内存回收可以采用纯RC的算法解决,但仍然会造成性能损伤。所以方舟编译器最终采用的垃圾回收方案包括了两个部分,一是针对特定的程序范围采用了循环引用标记来回收绝大多数的循环引用垃圾,二是采用传统的标记与回收策略作为最后的兜底方案。传统的GC策略被设置为在特定时刻启动,在平均负载下,24小时内只需要个位数的调用次数。
方舟编译器在华为的手机产品P30上投入商用,证明了Java静态编译技术在稳定性和性能上已经达到了可用的状态。目前方舟编译器已经迭代到了3.0版本,实现了对Java Script、Java的多语言的支持,并且正在开发对C/C++的支持。
[1]参见https://gitee.com/openarkcompiler/OpenArkCompiler/blob/master/doc/en/MapleIRDesign.md。