近些年来,云原生(Cloud Native)的概念席卷业界,其核心元素之一就是如何提高云上业务的响应速度。随着应用微服务化、容器化程度的不断提高,应用的执行响应速度也在不断提升,逐渐触及到了Java程序速度提升的天花板——Java自身的启动运行开销。
虽然Java从最初的单独解释执行演进到JIT(实时编译)和AOT(提前编译)后,在运行时peak performance和冷启动性能方面都取得了大幅进步,但如虚拟机启动、代码解释执行、JNI调用、反射开销、静态初始化检查开销等Java虚拟机和运行时本身的耗时点始终存在,并且耗时占比随着应用程序运行时间的降低而愈加突出。要进一步提高云原生应用的启动执行速度,突破Java自身的启动运行开销性能瓶颈,就必须在传统的Java程序编译启动执行过程之外,另辟蹊径。
Java静态编译技术是一种激进的AOT技术,通过单独的编译阶段将Java程序编译为本地代码,在运行时无需传统Java虚拟机和运行时环境,只需操作系统类库支持即可。目前较成熟、受到业界广泛关注的开源Java静态编译器是由Oracle开发的GraalVM SubstrateVM(以下简称SVM),图 1展示了目前基于SVM的阿里巴巴静态编译项目的基本编译过程。
SVM遵循封闭性假设(closed-world assumption, 即运行时所需的所有内容必须在编译时提供),将应用程序代码及其所依赖的三方jar文件和Alibaba Dragonwell代码编译为本地机器码,同时也将用Java编写的Runtime编译为机器码,两者被编到一个elf可执行文件或so共享库文件中,实现代码的自举。运行阶段只需直接执行elf文件,或者在其他C/C++程序中调用so文件中发布的API即可。
图 1:SVM静态编译基本过程
可以认为静态编译技术实现了Java语言与原生Native程序的“合体”,将原本的Java程序编译成为了一个自举的具有Java行为的原生Native程序,由此兼有Java程序和原生Native程序的优点。
从Java程序角度看,经过静态编译后Java程序具有更快的启动速度、更低的内存占用、更小的发布体积,在云上部署时从服务拉起到响应用户的请求所需的时间更短,非常适合云原生的需求。
以蚂蚁开源中间件项目sofastack的服务注册中心Meta节点应用为例,该项目验证了静态编译在Serverless应用上的可行性。图 2展示了以传统Java方式运行和静态编译程序运行的几项数据的比较。
图 2:Safostack注册中心Meta节点静态编译前后性能对比图
左上图的服务启动时间指服务完全启动到可以接受用户请求的状态所需的时间;右上图的可执行文件大小指包含了运行Java程序所需的所有依赖资源的fat jar包和静态编译出的elf可执行文件的大小比较,虚线框代表没有显式打入fat jar的Alibaba Dragonwell;底图的运行时内存消耗则是以相同压力请求测试时,两个版本应用各自的内存使用情况。从图中可见静态编译的代码在这些指标上较传统Java程序有质的提升,服务启动时间降低了17倍,可执行文件大小降低了3.4倍,运行时内存降低了一半。
从Native原生代码角度看,在静态编译的帮助下开发人员可以用Java编写出Native代码,较以往的C/C++开发效率更高、为同一业务维护多个语言版本的成本更低。
在这方面的探索中,阿里巴巴发布了静态编译版本的RocketMQ客户端,为RocketMQ 进一步Serverless化提供了语言无关的运行时保障,实现了一套内核的多语言支撑。图 3展示了RocketMQ客户端的多语言维护的示意。
图 3:RocketMQ客户端多语言实现示意图
在引入静态编译技术之前,同一需求需要用两种语言分别实现不同的SDK,最终运行在不同的环境中,开发和维护成本高。在静态编译的支持下,同一需求只用Java单一语言开发,然后沿图中绿色路径静态编译得到Native Code。当然此外还需要一部分适配工作,将拟暴露的API用SVM定义的C-Java转化协议包装起来。
与先前单独编写C++客户端相比,静态编译方案给多语言客户端的快速开发和后续管理带来了诸多优势:
目前静态编译版本的RocketMQ客户端已稳定可靠地服务阿里巴巴集团内部以及阿里云超过10+ 业务场景,包括网络延迟在内的客户端启动时间相比Java原生客户端提升30%,达到与C++版应用同等的启动性能。
然而世界上没有银弹,静态编译技术也不例外。兼有Java和原生Native代码两者的优势是以牺牲部分Java动态特性为代价换取来的,表格 1列出了在SVM中受限制的Java特性。封闭性假设要求在编译时必须获取运行时所需的全部信息,这与Java的动态性相矛盾,是静态编译对传统Java限制最大的部分。代码Native化是指由于丢弃了Bytecode,传统上基于Bytecode的特性便不再被支持。缺少工具指缺少调试静态编译后代码的工具,如Heap Dump、Thread Dump、代码调试等功能在SVM社区版中都没有提供。
原因分类 | 存在限制的特性 | 具体解释 |
---|---|---|
封闭性 | 反射、动态类加载、动态代理、JNI反射、序列化反序列化、MethodHandler | 编译时需要完全掌握运行时的信息 |
代码native化 | 插桩、JVMTI、agent | 编译为本地代码后已不存在bytecode |
缺少工具 | Heap dump,Thread dump、调试 | 部分存在于企业版中,社区版为提供 |
表格 1:SVM Java特性限制一览
可以看到SVM静态编译仅支持了传统Java特性的子集,而且对生产级的应用还缺少良好的工具支持。阿里巴巴正在结合自己的业务场景与SVM开源社区紧密合作,以Alibaba Dragonwell和SVM为基础,积极探索如何缩小静态编译和传统Java之间的鸿沟。
一方面逐步扩展静态编译能够支持的特性子集,使静态编译能够适用于更多的业务场景。比如针对JDK原生序列化反序列化的需求,通过预执行输出动态加载的类,将其加入静态编译的代码范围中,并自动生成需要的反射配置信息提供给编译器,最终实现对序列化反序列化特性的支持。另一方面探索适合面向静态编译的Java编程模型,帮助开发人员便捷地开发出适合静态编译的Java程序。
阿里巴巴对Alibaba Dragonwell进行静态化剪裁,一边定义了使用反射、动态类加载等不完全支持特性时的规则,并通过javac做强制检查。一边将静态编译完全不能支持的特性从Alibaba Dragonwell中去除,帮助开发人员以静态编译的思维开发新的Java程序。最后增加了对Thread dump,Heap dump支持,并开发了针对阿里巴巴业务场景的更有效的GC算法。这些对业务场景的不断实验和打磨,在成熟后也会贡献反哺到社区。
静态编译技术是一种兼具传统Java程序和本地原生代码程序二者优点的技术。以静态编译的原生代码助力云原生应用,既保持了传统Java的开发流程和效率,又消除了Java自身固有的性能瓶颈,显著改善服务拉起响应请求的速度,阿里巴巴也通过自身实践,证明了静态编译技术在实际生产中的可行性。
在长期的Java开发实践中,阿里巴巴对OpenJDK不断优化总结开发出了其下游Alibaba Dragonwell JDK,现已运行在100,000+服务器上,为阿里巴巴的各项业务提供基础服务。相信随着Alibaba Dragonwell静态编译版本进一步成熟,将能够支持更多原生代码应用部署上云。
作者介绍: 林子熠,阿里巴巴集团JVM团队技术专家。上海交通大学软件工程专业工学博士,毕业后加入华为编译器与编程语言实验室参与方舟编译器的研发工作,现于阿里巴巴JVM团队主要负责Java静态编译技术在Java服务端的开发与应用。
领取专属 10元无门槛券
私享最新 技术干货