vlambda博客
学习文章列表

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

来源 | QCon全球软件开发大会    嘉宾 | 林子熠     整理 | 李

Java 语言自从诞生以来就被打上了“慢”的标签。经过 25 年的发展,Java 程序的峰值性能在实时编译(JIT)技术的支持下已能超越 C/C++ 程序,但实时编译仍无法解决冷启动速度慢的问题。在云原生的场景下,往往需要快速拉起新的服务以响应用户持续增长的请求,此时服务的启动时间就显得至关重要。


阿里探索了两种不同维度上的冷启动加速技术,经过双 11 大促的检验都取得了良好的效果:AppCDS 技术在传统 Java 环境的维度上,利用类数据共享特性改进启动速度和减少内存开销;静态编译技术则在更为激进的维度上,将 Java 程序提前编译为二进制机器码,实现以 Native Code 的速度启动 Java 程序,带来最多两个数量级的启动性能提升。


阿里JVM 团队技术专家林子熠博士在最新出版的《GraalVM与Java静态编译:原理与应用》一书中,揭秘Oracle GraalVM中Java静态编译技术的特性、实现原理、应用与调试技巧,以突破Java“冷启动”桎梏,实现启动性能“质”的飞跃。

作者介绍 :林子熠 博士

  • 《GraalVM与Java静态编译:原理与应用》作者

  • 上海交通大学软件工程博士

  • 美国伊利诺伊大学香槟分校(UIUC)访问学者

  • 中国计算机学会系统软件专委会委员

  • 曾任华为方舟编译器前端核心

  • 现就职于阿里巴巴集团JVM团队,负责GraalVM的Java静态编译和静态分析技术在阿里的落地应用

  • Graal开源社区主要贡献:

序列化、动态类加载、独立静态分析工具



以下是来自林老师QCon北京大会的分享,以期帮你实现云原生场景下 Java 快速冷启动(下文以林子熠老师第一人称叙述)

Java 诞生至今的 25 年里,凭借其峰值性能高、语言功能强、生态支持好等特点赢得了语言市场的霸主地位。但 Java 冷启动开销大,而云原生时代下的应用程序短小,启动频繁,冷启动问题的解决机不容发。

冷启动问题根因及应对

下图为典型 Java 应用的生命周期:

如图,Java 应用生命周期分为 5 个阶段:VM 初始化阶段、APP 初始化阶段、APP 初活跃阶段、APP 稳定执行期、结束阶段。

VM 初始化(图中红色)和 Class loading(图中蓝色)的开销为冷启动的根因。阿里巴巴实现了两类改造:一类为改良型技术,调整优化现有 Java 的框架和运行模型,另外一类为革新型的技术,摆脱原有 Java 框架另起炉灶。

EagerAppCDS

改良型技术中,阿里巴巴主要实现了基于传统 CDS(Class Data Sharing)的 EagerAppCDS。传统 CDS 包括 mark、Klass*、fields 三部分,如下图所示:

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

Klass* 指针指向内存中 class 实例 InstanceKlass,该实例为 bytecode Class Loading 解析后生成的,但在多次解析时内容均不变,因此可固定存储磁盘文件 Shared Archive 中,下次运行时从文件中可省略解析直接读取,实现提速。

面对 system class,CDS 可根据 name 快速匹配,但面对 customized class 时,JVM 无法辨认 customized class loader 的身份,因此需在 classpath 上扫描 jar 包,根据 name、crc 校验后方可完成匹配。jar 包即为 libs 包,包含大量 I/O 操作,开销大。EagerAppCDS 用 identity 固定了 customized class loader 的名字,可直接通过 identity+name 匹配找到所需 class。

下图为 EagerAppCDS 在阿里巴巴内部实践的脱敏数据,如图所示性能提升效果从 12%~95% 不等。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

EagerAppCDS 虽未开源但已在阿里云 SAE(Serverless 微服务 PaaS 平台)上线。线上可公开实测数据中应用启动耗时降低 5%~45%,提升效果与启动时加载类数量成正比。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

除此之外,我们还实现了以下改进型技术:

  • JWarmup:共享预热后的 code cache,减小 JIT 开销;

  • PGO AOT:提前准备 runtime 信息,指导改进 AOT 代码质量;

  • Class Preinit:类预先初始化,降低运行时初始化类的开销。

Graal VM 静态编译技术

革新型技术中,阿里巴巴采用了基于 Graal VM 的静态编译技术。Graal VM 为 Oracle 主导的基于 Java 的开源高性能多语言平台:C++、Kotlin、python 等多种语言可通过 Truffle 框架运行在 GraalVM 上,Java 和其他 JVM 语言(如 Groovy、Kotlin 和 Scala 等)编译成 bytecode 后可直接运行。

Substrate VM(SVM)为 Graal VM 的静态编译组件,可将 Java 程序静态编译为可执行文件或共享库文件 Native Image,实现直接编译 Java 代码。

Java 最初依靠解释器实现无需编译实时执行;该解释器性能较差,因此引入了 JIT(Just In Time)实时编译技术,将高热度函数送到编译器中编译;为解决编译器开销大的问题,引入了 AOT(Ahead Of Time)编译,提前编译部分代码;AOT 缺乏 runtime 数据,运行后即丧失转为 JIT 的机会,运行速度慢;静态编译技术将 AOT 扩大,彻底摒弃 JVM,由 SVM 提供运行环境。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

静态编译基本原理

传统 Java 执行模型如下图所示:Application(应用本身)在 libs 的支持下运行在 JDK 上在 JVM 中执行。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

静态编译在 Graal Compiler 编译器中编译 Application、libs、JDK,同时编译 Substrate VM Runtime,获得 Native Image。Native Image 包含 code(编译后的代码)和 Image heap(存储数据)两部分。Image heap 为运行时 heap 的起点,直接读取 Image heap 可以提高运行时的性能。

静态编译必须遵循封闭性原则 (the closed-world assumption),即所有运行时信息均需在编译时可见。该原则带来两个基本问题:如何确定封闭的边界?如何处理 Java 的动态特性?

静态分析

Java bytecode 编译为 Native code 时,代码抽象性降低体积增大,如若编译所有代码,Native Image 体积将过于庞大,因此需确定封闭边界。SVM 通过静态分析上实现了从给定入口开始确定程序可达范围的功能。

该技术应用广泛,例如 main 函数调用 Virtue call 必须先明确其 type,type 和 Virtue call 有时可唯一绑定,但通常不能唯一绑定。此时使用静态分析技术,可明确 Virtue call type 的可能范围,实现封闭。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

受静态分析本身的特性和能力所限,静态分析得到的可达代码集合(蓝色)略大于实际执行代码集合(绿色)。静态分析精度越高、冗余越少、image 越小。

基于配置的动态特性支持

静态分析无法分析出 Java 的许多动态特性运行时的行为,如反射、动态代理、JNI、序列化(阿里巴巴贡献,从 21.0 开始支持)、动态类加载(阿里巴巴贡献,patch 已经通过评审)等。此时需提前获取所需信息,方可封闭此类动态特性的触达范围——即需基于配置进行动态特性支持。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

以反射为例。SVM 提供了 native-image-agent,可记录 APP 运行时所有的反射。编译时只需解析配置文件,即可注册反射目标,扩大编译范围;同时获取反射信息后可放入 ReflectionData 缓存中,将反射调用替换为直接调用。运行时如遇反射可查找 ReflectionData,获取目标值,通过 Method.invoke 直接调用目标函数。

下图为通过静态编译和传统 Java 两种方式,分别用反射调用空函数 30 次性能对比测试结果:

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

由于峰值过高,该图进行了对数修正。传统 Java 编译空函数耗时(深蓝色)为 3000ns,峰值由于反射开销为 4000ns,静态编译后(深蓝色)稳定在 150ns 内。

SVM 的静态编译实现的编译优化包括标准优化如:Method inlining, constant folding and arithmetic optimization, loop optimization, partial escape analysis 等。

此外还有因为静态分析而引入的新的编译优化,例如未被标为 final 的 field 通过静态分析发现只读不写,即可当作常量处理,做常量折叠等优化;又如,静态分析出某虚函数 type 唯一绑定,即可优化为直接调用,进而实现 inline;再如,消除部分编译时已知变量类型状态的类型检查和空指针检查。

主要运行时组件

静态编译由于所有的类均已被编译因此只有一个类加载器,实际只执行类查找功能。

传统 Java 一边检查异常一边运行,如遇异常直接处理即可。SVM 考虑到在不同平台兼容性,异常处理采用非信号处理机制:检测无错方可正常运行。该检测对性能影响小。

此外,静态编译的 GC 为 Oracle 开源版本中的单线程 stop-and-copy 顺序 GC,性能一般。

性能对比 - 实验室数据

下图为 Graal VM 官方的实验数据:

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

如上图所示,在只执行 Hello world 程序时,Native Image 性能次于 C,与 Go 相当,远快于传统 JDK;内存使用次于 C,只有 Go 的一半,远低于传统 JDK,具有高性能低内存占用的优点。图中红色数据为受测语言数据除以 Native Image 数据所得比值。

性能对比 - 实际场景数据

Javac 为 Java 编写的编译器:可以在 Java 程序中来调用 API 编译,也可用 stand alone 工具编译。通过 API 调用,实际上已完成 VM 启动,因此两者对比可观察冷启动带来的性能差异。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

通过 API 调用 Javac 耗时 250ms,使用 Native Image 后耗时达到 35ms,实现了 1 个数量级的飞跃。

Javac 中使用的反射、Serverless 较少,其他项目静态分析性能提升效果更加显著。下图为基于 spring boot 的应用 greeting-service 部署在阿里云函数计算平台上的数据。greeting-service 收到请求会返回“ hallo”,功能简单但需要 spring boot 全流程支持。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

如图所示,Native Image 静态编译(橙色)相比传统 Java(蓝色):内存占用从 128MB 降至 21MB;实际第一次调用耗时从 454 ms 降至 4.27 ms,提升了两个数量级;阿里云服务计费从 500ms 降至 100ms,事半功倍。

静态编译局限性

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

静态编译的局限性如上表所示:

  • 为实现封闭性,反射、动态代理、JNI、序列化、动态类加载均需要通过配置支持;

  • 不支持 InvokeDynamic(开发人员使用)、Method Handles(开发人员使用)、Security Manager、多 classloader、Finalizers、过时 Thread 函数(如 Thread.stop())等;

  • Java 程序被静态编译后不再保留 bytecode,因此存在监控、调试方面的问题:不支持 JVMTI、JMX、agent,只能使用 GDB 调试,无法通过 Eclipse IDE、IntelliJ IDEA 等调试。

GraalVM 生态发展

GraalVM 静态编译目前生态如下:

  • 阿里云:通过阿里云函数计算平台进行支持部署 serverless Native Image 应用,通过 Apache RocketMQ 为 C++ 客户端提供使用静态编译的 Java 共享库;

  • Spring 社区:发布了针对于静态编译 Spring-Native beta 版本,完全支持 Spring 的运算;

  • MICRONAUT:实现了支持 Native Image 的去反射微服务框架;

  • Facebook & Twitter:均在生产环境下使用 Graal 编译器代替 C2 编译器。

总之,在 Serverless 场景下 Java 的冷启动问题与应用对快速响应、实时扩展的需求形成突出矛盾。阿里巴巴一方面在现有技术上不断改进,最终形成突破:EagerAppCDS 提升最多 45% 的启动速度;另一方面积极参与开源社区探索创新型的前沿技术,打磨成熟用于实践:GraalVM 静态编译技术最多提升百倍启动速度。但 GraalVM 存在兼容性和改造成本的问题,适合新项目。




423 · 世界读书日之际,我们邀请到了《GraalVM与Java静态编译:原理与应用》作者林子熠分享Java冷启动性能飞跃之道:Java静态编译技术,为你探究其中的奥秘

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?


▌直播间福利:


邀请好友观看「Java冷启动性能飞跃之道:Java静态编译技术」直播,将有机会免费领取

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?《GraalVM与Java静态编译:原理与应用》/Java核心技术(原书第11版》等纸书

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级? 免费获得华章千余种有声书、电子书VIP季卡









【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?



《Java核心技术(Core Java)

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

这本书就像一张高精地图,展示了Java编程语言这座“热门城市”的知识体系全貌,从Java语言的核心概念、基础语法、学习路线,到各个重要特性、异常处理、开发方法等都有全面和详细的介绍。


对于学习者最重要的是动手实践,实践是最好的老师,本书随内容提供了大量示例代码,可以跟着这些示例由浅入深地进行实践。书中还给出了大量注释、提示和警告,帮助你写出高质量代码。作者凯.霍斯特曼亲自为本书录制了学习视频,对书里的重点和难点一一作了讲解,相关资源,在B站Java技术核心技术站”可免费观看。


相信在学习Java的道路上有了《Java核心技术》这本书的辅助,大家的学习一定可以做到事半功倍。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?
Effective Java中文版(原书第3版)
【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?
“我很希望我10年前就能拥有这本书。有人可能认为我不需要任何Java方面的书籍,但是我需要这本书。”——Java之父James Gosling
你是否正在寻找一本能够更加深入地了解Java编程语言的书,以便编写出更清晰、更正确、更健壮且更易于重用的代码?本书再适合不过了!这是一本分享经验并指引你少走弯路的经典著作,针对如何编写高效、设计优良的程序提出了最实用、最权威的指导方针,通过90条经验法则,探索新的设计模式和语言习惯用法,帮你更加有效地使用Java编程语言及其基本类库。
适读人群 :已经掌握Java核心技术的程序员,想更加深入地了解Java编程语言,成为一名更优秀、更高效的Java开发人员。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?
构建高质量软件:持续集成与持续交付系统实践
【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?
“快”的前提是高质量的交付,而高质量的交付则离不开一套稳健的持续(continuous)环境。所谓的持续,并不是一直运行(always running),而是具备持续运行(always ready to run)的能力。因此,基于“持续”概念衍生出了持续集成(CI)、持续交付和持续部署(CD)等工程实践,在每一个细分领域中又诞生了琳琅满目的工具和工具组合。
如何在如此之多的工具中挑选出合适的工具集来构建自己的“持续”环境呢?这正是本书所要解决的问题。只有真正理解了什么是持续集成、持续交付和持续部署,才能理解单元测试、功能测试,以及集成环境中每一个环节的作用和重要性。本书将从理论、实践的角度出发,为读者介绍CI/CD环节中不同工具的使用和整合,使读者能够快速搭建起适合自己团队的持续构建环境。


【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?
Java多线程编程核心技术(第3版)

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?
现代软件规模大、实时性要求高,所以掌握提升处理和响应速度的多线程技术势在必行。本书涵盖多线程编程的核心库、方法、原理,透彻讲解了高并发的本质与应对方法,帮助读者解决高并发环境下的业务瓶颈。第3版迎来重大更新,新增适合微服务与分布式开发的并发集合框架与Java线程池知识。

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?

Spring Data JPA:入门、实战与进阶

【干货】云原生时代,阿里如何让Java冷启动提速两个数量级?
资深架构师多年一线JPA开发实战经验总结,资深专家多年经验总结,从入门到精通,从原理到实战,深入浅出地完整掌握Spring Data JPA,从而提升开发效率。 从基础知识、高阶用法与实例、原理在实战中的应用、思路扩展4大部分全面讲解Spring Data JPA的技术栈。



Java并发编程的艺术

《Java并发编程的艺术》采用循序渐进的讲解方式,从并发编程的底层实现机制入手,逐步介绍了在设计Java并发程序时各种重要的技术、设计模式与应用,同时辅以丰富的示例代码,使得开发人员能够更快地领悟Java并发编程的要领,围绕着Java平台的基础并发功能快速地构建大规模的并发应用程序。


好啦,接下来就是本次送书环节啦~上面给出的书单,小伙伴们留言说说你想要哪本以及理由,松哥会从中选出六位幸运小伙伴,满足你的愿望,周三晚上开奖~。