前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 大杀器来了,这玩意也太猛了!

Java 大杀器来了,这玩意也太猛了!

作者头像
why技术
发布2022-02-17 08:41:15
4140
发布2022-02-17 08:41:15
举报
文章被收录于专栏:why技术

你好呀,我是歪歪。

最近看了一本书,今天打算和大家聊聊 Graal VM 和 Java静态编译这个东西:

也许现在你对它还了解不多,我个人认为,这是云原生时代下 Java 的一次破釜沉舟的产物。甚至把视角放到 Java 的整个生命周期的角度的时候,这个东西也许就是 Java 的一个革命性的转折点。

2018 年 4 月,Oracle Labs 新公开了一项黑科技:Graal VM,从它的口号“Run Programs Faster Anywhere”就能感觉到一颗蓬勃的野心。

这句话显然是与 1995 年 Java 刚诞生时的“Write Once,Run Anywhere”在遥相呼应。

经过多年的演进,Java 语言的功能和性能都在不断地发展和提高,但是冷启开销较大的问题长期存在,难以从根本上解决。

本文先讨论冷启动问题的根本原因然后介绍一种新近提出的彻底解决Java冷启动问题的技术方案——Java静态编译技术

啥是冷启动?

所谓冷启动问题是指Java应用并不是即起即用的,而需要经过虚拟机初始化后才能达到可用状态,再经过程序预热才能达到最佳性能。

下图给出了Java程序的运行时性能随运行时间(实际上是代码重复执行次数)的变化示意图。

  • 横坐标是程序运行时间,时间越长代表程序中代码被重复执行的次数越多
  • 纵坐标是程序的响应时间,响应越快代表运行时性能越好。

可以看出程序响应能力分成了四个部分:

第一个阶段为无穷大,因为程序启动时需要首先初始化Java虚拟机,然后初始化应用程序,在这个阶段应用是不会有响应的。随后经过解释执行、C1实时编译和C2实时编译,应用的响应时间才从高、中到了低,最终进入稳定执行阶段。前三个阶段就是冷启动,也可以看作程序预热,最后一个阶段为稳定执行,此时的程序运行时性能最好。

在传统的单机或者服务器部署的场景中,冷启动问题并不明显。

一来是应用执行时间足够长,冷启动问题就被淡化了。

二来人们还可以提前将服务预热准备好,以最好的状态迎接用户的服务请求。

但是在云原生 Serverless 应用的场景中,首次请求必须经过无响应阶段,才会落在响应时间高的为位置,后续请求也会落在高的阶段,只有经过足够多的请求后才会逐渐落入稳定阶段。

冷启动问题使得 Java 在 Serverless 场景下无法与 Node.js、Go 等具有快速启动优势的的语言的竞争中,落于下风。

冷启动问题的根本原因

当我们执行一个 Java 应用程序时,看似是从主函数(Java可执行应用程序的入口是主函数)开始的,但实际需要在 JVM 初始化后才会调用 Java 主函数开始执行应用程序。

我们将前面的图片展示的抽象模型进一步细化,可以得到如下图所示的 Java程序的执行生命周期模型:

Java程序可以分为VM初始化(VM init)、应用初始化(App init)、应用预热(App active warmup)、应用稳定(App active steady)和关闭(shutdown)这 5 个阶段。

上图的横坐标代表应用执行的时间顺序。

纵坐标代表 CPU 利用率,各个颜色的区域代表该行为的 CPU 使用率,红色区域的 VM 表示 JVM、青色的 CL 代表类加载(Class Loading),白色的是实时编译(Just In Time,JIT),黄色的代表垃圾回收(GC),浅绿色代表解释执行应用程序,绿色代表执行经过 JIT 编译的应用代码。

从上图中可以看到各个阶段中花费时间最多的行为是什么。但需要注意的是这里的使用情况并不是按实际比例绘制的,而是只反映整体趋势的示意,因为具体的数据会随应用不同而变化。

从上图可以看到 Java 程序的运行生命周期是:

  • 首先启动 JVM,执行各种 VM 的初始化动作。
  • 然后调用 Java 程序的主函数进入应用初始化,此时才会开始通过解释执行方式运行 Java 代码,随着 Java 代码运行而同时开始的还有 GC,JIT 会在出现热点函数时才开始。
  • 当程序初始化完成后,开始执行应用程序的业务代码,此时才算进入了程序执行的预热阶段,这个阶段会有大量的类加载和 JIT 编译行为。
  • 当程序被充分预热后,就进入了运行时性能最好的稳定阶段,此时的理想状态是只有应用本身和 GC 在运行,其他的行为都已渐渐退出。
  • 最后是关闭应用,各个行为次第结束。

Java 语言最初被认为是一种解释型语言,因为 Java 源代码并非被先编译为与机器平台相关的汇编代码再执行,而是先编译为与平台无关的字节码(bytecode),然后由 JVM 解释执行。

解释执行是由 JVM 将字节码逐条翻译为汇编代码,然后执行的过程。

经过解释的代码缺少编译优化,因此运行时性能较低。不过解释执行非常灵活,可以支持诸如动态类加载这样的动态特性。

Java 可以在运行时解释执行一段在编译时尚不存在的代码,这种特性对于编译执行类型的语言来说是难以想象的。

为了解决运行时性能低的问题,Java 引入了实时编译技术(JIT,Just In time),在运行时将热点函数编译为汇编代码,当程序再次运行到经过实时编译的函数时,就可以执行经过编译和优化的汇编代码,而不再需要解释执行了。

由于编译是在运行时进行的,因此 JIT 编译器可以获得代码实际运行的路径、热点和变量值等信息,基于此可以做出非常激进的编译优化,从而获得执行效率更高的代码。

OpenJDK 使用的 JIT 编译器分为 C1 和 C2,前者编译优化较少,但是编译所消耗资源也较少。后者编译得到的代码性能最好,但是编译消耗的资源也较多。

现在的Java程序基本都是采用解释执行加JIT执行的混合模式,当函数执行次数较少时解释执行,而当函数的执行次数超过一定阈值后再 JIT 执行,从而实现了热点函数 JIT 执行、非热点函数解释执行的效果。

不过既然 JIT 带来了非常显著的性能优势,为什么不全部采用 JIT 方式呢?

因为编译优化本身是需要占用系统资源的资源密集型运算,它会影响应用程序的运行时性能,在实践中甚至出现过 JIT 线程占用过多资源,导致应用程序不能执行的状况。

此外,如果代码执行的次数较少,编译优化代码造成的性能损失可能会大于编译执行带来的性能提升。

所以冷启动问题的原因有两点

  • 一是 Java 的虚拟机模型机制。
  • 二是从解释执行到 JIT 执行的分层次执行模型。

这两点在当前的 Java 模型下是无法更改的,它们都是 Java 运行时的基石。

如何解决冷启动问题

前面提到的这个问题并不是无解的,我们可以换个角度思路思考。

Java 虚拟机的主要作用是提供跨平台能力,以支持与平台无关的 Java 字节码可以在不同的操作系统中运行。

解释执行、JIT 执行等问题都是由此衍生而来的。

如果我们并不需要跨平台能力,是不是可以将 Java 程序直接编译为目标平台的机器码,然后提供必要的运行时支持,让它以操作系统原生程序的形式运行呢?

如此一来就彻底解决了冷启动问题。

答案是肯定的,这就是Java的静态编译技术。

Java 静态编译是指将 Java 程序的字节码在单独的离线阶段编译为汇编代码,其输入为 Java 的字节码,输出为操作系统本地原生程序。

“静态”是相对传统 Java 程序的动态性而言的,因为传统 Java 程序是在运行时动态地解释执行和 JIT 编译,而静态编译需要在执行前就静态地完成程序的编译。

目前由 Oracle 开发的高性能跨语言运行时框架开源项目 GraalVM 中就提供了Java 静态编译所需的编译工具链、编译框架、编译器和运行时等全套支持,并且已经达到了生产可用的程度。

GraalVM的静态编译的基本原则是封闭性假设(closed world assumption),要求编译器在编译时必须掌握运行时所需的全部信息。

换句话说,就是运行时不能出现任何编译时未知的内容。

这是因为应用程序的可达范围在静态编译时被限定了,因为没有了类加载器、解释器等组件,不能在运行时解析和执行任何动态引入的类。

与传统 Java 运行模型相比,GraalVM 的静态编译运行模型有两大特点:

一是静态编译后的可执行程序已经是本地程序,而且自包含了轻量级运行时支持,因此不再额外需要 Java 虚拟机。

没有了 JVM,自然也就消除了前面图片中的响应时间无穷大阶段,使得应用程序达到即起即用的状态。另外,因为 JVM 的运行也需要消耗一部分内存,去掉 JVM 后应用程序的内存占用也大幅降低。

二是静态编译后的程序也经过了众多的编译优化,运行时不再需要经过解释执行和 JIT 编译,既避免了解释执行的低效,也避免了 JIT 编译的 CPU 开销,还解决了传统 Java 执行模型中无法充分预热,始终存在解释执行的问题,因此可以保证应用程序始终以稳定的性能执行,不会出现性能波动。

这两个基本特点解决了 Java 程序冷启动问题—JVM 初始化的开销和从解释执行到 JIT 编译执行的开销,因此静态编译后的 Java 程序可以获得极速启动的效果。

下图给出了 OpenJDK 和静态编译后的 Java 程序的性能对比示意,其中蓝色线条为 OpenJDK 的运行时性能变化情况,红色线条为社区版 GraalVM 静态编译后的程序运行时性能变化情况。

可以看到经过社区版 GraalVM 的静态编译后的 Java 应用的性能稳定地处于 OpenJDK 的 C1 编译器的水平。

而商业版的 GraalVM 静态编译甚至可以使程序达到 C2 编译器的编译后的性能水平。

由此可见,Java 静态编译技术能够彻底解决 Java 冷启动问题,使得 Java 语言在云原生应用的浪潮中继续保持强大的竞争力,可谓是 Java 语言的“大杀器”了。

书籍推荐

目前关于 GraalVM 静态编译的大多数资料都是开发团队发布的技术文档、博客和 GitHub 上的开发相关问题讨论,而缺少系统全面性的资料介绍,尤其缺乏中文资料。因此国内的广大程序开发者和技术爱好者对其并不了解。

为了填补这方面的空白,使读者能够系统性了解并掌握 GraalVM 静态编译技术。

推荐阅读这本《GraalVM 与 Java 静态编译:原理与应用》。

这本书的作者是林子熠博士,现就职于阿里,专注于 Java 静态编译方向。主要负责 Java 静态编译技术在阿里巴巴生态中的应用,并以落地实践中发现的问题为切入点,向 GraalVM 社区贡献了多项重要特性,是 GraalVM 社区建设的积极参与者;曾作为核心人员,负责将华为方舟编译器前端的 Java 字节码和 Art Dex 字节码转换为方舟中间语言的开发工作。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 why技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 啥是冷启动?
  • 冷启动问题的根本原因
  • 如何解决冷启动问题
  • 书籍推荐
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档