经过多年的演进,Java语言的功能和性能都在不断地发展和提高,但是冷启动开销较大的问题长期存在,难以从根本上解决。本文先讨论冷启动问题的根本原因,然后介绍一种新近提出的彻底解决Java冷启动问题的技术方案——Java静态编译技术

什么是冷启动?

所谓冷启动问题是指Java应用并不是即起即用的,而需要经过虚拟机初始化后才能达到可用状态,再经过程序预热才能达到最佳性能。图1给出了Java程序的运行时性能随运行时间(实际上是代码重复执行次数)的变化示意图。

图 1 Java程序运行时性能演化示意图

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

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

在传统的单机或者服务器部署的场景中,冷启动问题并不明显,一来是应用执行时间足够长,冷启动问题就被淡化了;二来人们还可以提前将服务预热准备好,以最好的状态迎接用户的服务请求。

但是在云原生Serverless应用的场景中,首次请求必须经过无响应阶段,才会落在响应时间高的为位置,后续请求也会落在高的阶段,只有经过足够多的请求后才会逐渐落入稳定阶段。冷启动问题使得Java在Serverless场景下无法与Node.js、Go等具有快速启动优势的的语言的竞争中,落于下风。


冷启动问题的根本原因

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

我们将图 1展示的抽象模型进一步细化,可以得到如图 2所示的Java程序的执行生命周期模型:Java程序可以分为VM初始化(VM init)、应用初始化(App init)、应用预热(App active warmup)、应用稳定(App active steady)和关闭(shutdown)这5个阶段。

图 2 Java应用程序的运行生命周期示意图

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

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

从图 2可以看到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,自然也就消除了图 1中的响应时间无穷大阶段,使得应用程序达到即起即用的状态。另外,因为JVM的运行也需要消耗一部分内存,去掉JVM后应用程序的内存占用也大幅降低。

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

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

图 3 给出了OpenJDK和静态编译后的Java程序的性能对比示意,其中蓝色线条为OpenJDK的运行时性能变化情况,红色线条为社区版GraalVM静态编译后的程序运行时性能变化情况,可以看到经过社区版GraalVM的静态编译后的Java应用的性能稳定地处于OpenJDK的C1编译器的水平。而商业版的GraalVM静态编译甚至可以使程序达到C2编译器的编译后的性能水平。

图 3 OpenJDK与GraalVM静态编译的Java程序性能对比示意图

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

关于作者

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

推荐阅读

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

推荐语: 业界多位知名专家鼎力推荐,一本书为你揭秘OracleGraalVM中Java静态编译技术,以突破Java“冷启动”桎梏,实现启动性能“质”的飞跃。本书是深度、系统研究Java静态编译技术的鲜见资料。

抽奖赠书

截止时间:2022年1月17日 17:00

如何抽奖:点击下方卡片,关注并回复关键词 :20220113

下次你更希望我们送哪本书呢?

留言告诉我们!

Java为什么冷启动开销大?我们又该如何解决?相关推荐

  1. java数据导出ex_大数据面试题(三)

    160.说说数据类型之间的转换: 1 ) 如何将字符串转换为基本数据类型? 2 ) 如何将基本数据类型转换为字符串? 答:1 ) 调用基本数据类型对应的包装类中的方法parseXXX(String)或 ...

  2. Java并发编程知识大汇总

    线程简介 什么是线程 现代操作系统调度的最小单元是线程,也叫轻量级进程,在一个进程里可以创建很多是线程,这些线程都有自己的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量. 之所以我们感觉不到 ...

  3. java面试笔试题大汇总

    java面试笔试题大汇总 JAVA相关基础知识 1.面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题 ...

  4. Java面试笔试题大汇总一(最全+详细答案)

    Java面试笔试题大汇总二(最全+详细答案)https://www.jianshu.com/p/f5120f1b75be Java面试笔试题大汇总三(最全+详细答案)https://www.jians ...

  5. 233网校java_java辅导:使用java模拟登陆考试大

    java辅导:使用java模拟登陆考试大 2009年1月5日来源:233网校网校课程 在线题库评论 分享到 public static void loginexamda() { URL url = n ...

  6. Java面试笔试题大汇总三(最全+详细答案)

    Java面试笔试题大汇总一(最全+详细答案):https://www.jianshu.com/p/73b6b3d35676 Java面试笔试题大汇总二(最全+详细答案)https://www.jian ...

  7. 东华大学java_东华大学2020秋《Java程序设计》期末大作业

    东华大学继续教育学院 2020年秋季学期 远程学历教育<Java程序设计>期末大作业 一.选择题(本大题共10小题,每小题 1分, 共10分) 1.    下列哪个不是面向对象程序设计的基 ...

  8. Java 语言中十大“坑爹”功能!

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源:https://www.sohu.com/a/35 ...

  9. java add offer_图解Java中的5大队列!(干货收藏)

    Java 中的队列有很多,例如:ArrayBlockingQueue.LinkedBlockingQueue.PriorityQueue.DelayQueue.SynchronousQueue等,那它 ...

最新文章

  1. JavaScript总结(七)
  2. git放弃修改放弃增加文件
  3. windows下批量修改文件(或文件夹)权限或所有者
  4. Solr的自动完成实现方式(第三部分:Suggester方式续)
  5. linux虚拟化管理平台,linux虚拟化管理
  6. Flutter之SafeArea
  7. 腾讯之困,QQ与微信各有各的烦恼
  8. 可以十倍地提高.NET 应用程序的速度集群存储器对象缓存控件NCache
  9. 怎么把一个gif表情包分解成多个?
  10. 小明左、右手中分别拿两张纸牌:黑桃10和红桃8.现在交换手中的牌。
  11. 深入理解计算机系统(第三版)家庭作业 第九章
  12. To invoke and to begin invoke, that is a question.
  13. 使用git从github上稳定下载项目
  14. 问路全球化,富途、老虎需要突围两个关键词
  15. TOM游戏h5营销案例分析-高空运鸡蛋
  16. 使用vuepress搭建静态博客
  17. python创建目录
  18. 唐纳德·A·诺曼《设计心理学2 与复杂共处 修订版》(高清带目录)pdf下载
  19. 三张图秒懂Redis集群设计原理
  20. 如何将.spl剥离成.emf文件格式

热门文章

  1. C++获取站点的ip地址
  2. Cracking the coding interview--Q1.7
  3. 时代亿信 认证墙-SID强身份认证产品
  4. Javascript中的陷阱大集合【译】
  5. Web Service 开发工具 gSOAP 简介
  6. python3 subprocess.check_output 执行shell命令 返回结果
  7. golang go build 报错 import cycle not allowed
  8. linux /proc目录文件详解
  9. Linux中error while loading shared libraries错误解决办法
  10. OPKG 软件包管理