Introduction

对于大部分应用开发者来说,Java编译器指的是JDK自带的javac指令。这一指令可将Java源程序编译成.class文件,其中包含的代码格式我们称之为Java bytecode(Java字节码)。这种代码格式无法直接运行,但可以被不同平台JVM中的interpreter解释执行。由于interpreter效率低下,JVM中的JIT compiler(即时编译器)会在运行时有选择性地将运行次数较多的方法编译成二进制代码,直接运行在底层硬件上。Oracle的HotSpot VM便附带两个用C++实现的JIT compiler:C1及C2。

与interpreter,GC等JVM的其他子系统相比,JIT compiler并不依赖于诸如直接内存访问的底层语言特性。它可以看成一个输入Java bytecode输出二进制码的黑盒,其实现方式取决于开发者对开发效率,可维护性等的要求。Graal是一个以Java为主要编程语言,面向Java bytecode的编译器。与用C++实现的C1及C2相比,它的模块化更加明显,也更加容易维护。Graal既可以作为动态编译器,在运行时编译热点方法;亦可以作为静态编译器,实现AOT编译。在Java 10中,Graal作为试验性JIT compiler一同发布(JEP 317)。这篇文章将介绍Graal在动态编译上的应用。有关静态编译,可查阅JEP 295或Substrate VM。

Tiered Compilation

在介绍Graal前,我们先了解HotSpot中的tiered compilation。前面提到,HotSpot集成了两个JIT compiler — C1及C2(或称为Client及Server)。两者的区别在于,前者没有应用激进的优化技术,因为这些优化往往伴随着耗时较长的代码分析。因此,C1的编译速度较快,而C2所编译的方法运行速度较快。在Java 7前,用户需根据自己的应用场景选择合适的JIT compiler。举例来说,针对偏好高启动性能的GUI用户端程序则使用C1,针对偏好高峰值性能的服务器端程序则使用C2。

Java 7引入了tiered compilation的概念,综合了C1的高启动性能及C2的高峰值性能。这两个JIT compiler以及interpreter将HotSpot的执行方式划分为五个级别:

  • level 0:interpreter解释执行

  • level 1:C1编译,无profiling

  • level 2:C1编译,仅方法及循环back-edge执行次数的profiling

  • level 3:C1编译,除level 2中的profiling外还包括branch(针对分支跳转字节码)及receiver type(针对成员方法调用或类检测,如checkcast,instnaceof,aastore字节码)的profiling

  • level 4:C2编译其中,1级和4级为接受状态 — 除非已编译的方法被invalidated(通常在deoptimization中触发),否则HotSpot不会再发出该方法的编译请求。

上图列举了4种编译模式(非全部)。通常情况下,一个方法先被解释执行(level 0),然后被C1编译(level 3),再然后被得到profile数据的C2编译(level 4)。如果编译对象非常简单,虚拟机认为通过C1编译或通过C2编译并无区别,便会直接由C1编译且不插入profiling代码(level 1)。在C1忙碌的情况下,interpreter会触发profiling,而后方法会直接被C2编译;在C2忙碌的情况下,方法则会先由C1编译并保持较少的profiling(level 2),以获取较高的执行效率(与3级相比高30%)。

Graal可替换C2成为HotSpot的顶层JIT compiler,即上述level 4。与C2相比,Graal采用更加激进的优化方式,因此当程序达到稳定状态后,其执行效率(峰值性能)将更有优势。

早期的Graal同C1及C2一样,与HotSpot是紧耦合的。这意味着每次编译Graal均需重新编译HotSpot。JEP 243将Graal中依赖于HotSpot的代码分离出来,形成Java-Level JVM Compiler Interface(JVMCI)。该接口主要提供如下三种功能:

  • 响应HotSpot的编译请求,并分发给Java-Level JIT compiler

  • 允许Java-Level JIT compiler访问HotSpot中与JIT compilation相关的数据结构,包括类,字段,方法及其profiling数据等,并提供这些数据结构在Java层面的抽象

  • 提供HotSpot codecache的Java抽象,允许Java-Level JIT compiler部署编译完成的二进制代码

综合利用这三种功能,我们可以将Java-Level编译器(不局限于Graal)集成至HotSpot中,响应HotSpot发出的level 4的编译请求并将编译后的二进制代码部署到HotSpot的codecache中。此外,单独利用上述第三种功能可以绕开HotSpot的编译系统 — Java-Level编译器将作为上层应用的类库直接部署编译后的二进制代码。Graal自身的单元测试便是依赖于直接部署而非等待HotSpot发出编译请求;Truffle亦是通过此机制部署编译后的语言解释器。

Graal v.s. C2

前面提到,JIT Compiler并不依赖于底层语言特性,它仅仅是一种代码形式到另一种代码形式的转换。因此,理论上任意C2中以C++实现的优化均可以在Graal中通过Java实现,反之亦然。事实上,许多C2中实现的优化均被移植到Graal中,如近期由其他开发者贡献的String.compareTo intrinsic的移植。当然,局限于C++的开发/维护难度(个人猜测),许多Graal中被证明有效的优化并没有被成功移植到C2上,这其中就包含Graal的inlining算法及partial escape analysis(PEA)。

Inlining是指在编译时识别callsite的目标方法,将其方法体纳入编译范围并用其返回结果替换原callsite。最简单直观的例子便是Java中常见的getter/setter方法 — inlining可以将一个方法中调用getter/setter的callsite优化成单一内存访问指令。Inlining被业内戏称为优化之母,其原因在于它能引发更多优化。然而在实践中我们往往受制于编译单元大小或编译时间的限制,无法无限制地递归inline。因此,inlining的算法及策略很大程度上决定了编译器的优劣,尤其是在使用Java 8的stream API或使用Scala语言的场景下。这两种场景对应的Java bytecode包含大量的多层单方法调用。

Graal拥有两个inliner实现。社区版的inliner采用的是深度优先的搜索方式,在分析某一方法时,一旦遇到不值得inline的callsite时便回溯至该方法的调用者。Graal允许自定义策略以判断某一callsite值不值得inline。默认情况下,Graal会采取一种相对贪婪的策略,根据callsite的目标方法的大小做出相应的决定。Graal enterprise的inliner则对所有callsite进行加权排序,其加权算法取决于目标方法的大小以及可能引发的优化。当目标方法被inline后,其包含的callsite同样会进入该加权队列中。这两种搜索方式都较为适合拥有多层单方法调用的应用场景。

Escape analysis(逃逸分析,EA)是一类识别对象动态范围的程序分析。编译器中常见的应用有两类:如果对象仅被单一线程访问,则可去除针对该对象的锁操作;如果对象为堆分配且仅被单一方法访问(inlining的重要性再次体现),则可将该对象转化成栈分配。后者通常伴随着scalar replacement,即将对对象字段的访问替换成对虚拟局部操作数的访问,从而进一步将对象由栈分配转换成虚拟分配。这不仅节省了原本用于存放对象header的内存空间,而且可以在register allocator的帮助下将(部分)对象字段存放在寄存器中,在节省内存的同时提高执行效率(内存访问转换成寄存器访问)。

Java中常见的for-each loop是EA的一大目标客户。我们知道for-each loop会调用被遍历对象的iterator方法,返回一个实现interface Iterator的对象,并利用其hasNext及next接口进行遍历。Java collections中的容器类(如ArrayList)通常会构造一个新的Iterator实例,其生命周期局限于该for-each loop中。如若Iterator实例的构造函数以及hasNext,next方法调用(连同它们方法体中以this为receiver的方法调用,如checkForComodification())都被inline,EA会认为该实例没有逃逸,并采取栈分配及scalar replacement。

理想情况下,Foo.bar会被优化成如下代码:

HotSpot的C2便已应用控制流无关的EA实现scalar replacement。而Graal的PEA则在此基础上引入了控制流信息,将所有的堆分配操作虚拟化,并仅在对象确定逃逸的分支materialize。与C2的EA相比,PEA分析效率较低,但能够在对象没有逃逸的分支上实现scalar replacement。如下例所示,如果then-branch的执行概率为1%,那么被PEA优化后的代码在99%的情况下并不会执行堆分配,而C2的EA则100%会执行堆分配。另一个典型的例子是渲染引擎Sunflow — 在运行DaCapo benchmark suite所附带的默认workload时,Graal的PEA判定约27%的堆分配(共占700M)可被虚拟化。该数字远超C2的EA。

Using Graal

在Java 10 (Linux/x64, macOS/x64)中,默认情况下HotSpot仍使用C2,但通过向java命令添加-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler参数便可将C2替换成Graal。

Oracle Labs GraalVM是由Oracle Labs直接发布的JDK版本。它基于Java 8,并且囊括了Graal enterprise。如果对源代码感兴趣,可直接签出Graal社区版的GitHub repo。源代码的编译需借助mx工具及labsjdk(注:请下载页面最下方的labsjdk,直接使用GraalVM可能会导致编译问题)。

在graal/compiler目录下使用mx eclipseinit,mx intellijinit或mx netbeansinit可分别生成Eclipse,IntelliJ或NetBeans的工程配置文件。

广告时间

没办法,好多朋友要找我免费打点广告,这次还不趁机说说估计会找我麻烦了,有需要的自取,打扰各位了

  • 前不久我们发布的JVM参数分析产品XXFox,可以一键生成JVM参数,并能检查你们现有JVM参数是否存在一些问题,有没有体验过的可以体验一下,http://xxfox.perfma.com,可以点击下面的【原文阅读】进去体验

  • 我们团队Base在杭州,希望能围绕JVM做一些性能分析相关的产品,有兴趣的小伙伴请加入我们

  • 相关阅读:

  • 深入解读 Java 9 新特性

  • Java 微服务框架新选择:Spring 5

本文作者二维码

Java10来了,来看看它一同发布的全新JIT编译器相关推荐

  1. 神器!微软发布 Python 的 JIT 编译器:Pyjion!

    出品 | OSC开源社区 ‍用于 Python 3.10 及以上版本的嵌入式 JIT 编译器 Pyjion 已发布 1.0 版本. Pyjion 拥有以下特性: 配置文件引导的 JIT 编译器 原生 ...

  2. Code-First Migrations随Entity Framework 4.3一同发布

    Entity Framework 4.3 版本终于为开发者带来了迁移(Migrations)功能,从此以后使用EF不必依赖于单独预发布的迁移库了. 什么是EF迁移呢?如果你正在使用Entity Fra ...

  3. 华为平板鸿蒙发布,华为新款平板与P50一同发布!有望搭载鸿蒙系统

    芯片的供应不足,荣耀的独立.....今年华为可谓是经历了惨重的打击.好在独立之后的荣耀和高通进行了合作,近日又有消息传出荣耀和联发科有了合作.如此一来,荣耀的芯片问题也得以解决,华为在芯片方面的压力也 ...

  4. Newbe.Claptrap 0.9.4 发布,全新构建

    Newbe.Claptrap 0.9.4 发布,全新构建,全新内容,全新体验. 简介 此次的版本更新虽然只是一个副版本更新.但实际上我们带来了非常多全新的内容.全方位将本框架带入到一个新的次代.我们希 ...

  5. MRoot 2.2 发布,全新 UI 界面,更好的集群

    开发四年只会写业务代码,分布式高并发都不会还做程序员? >>>   MRoot 2.2 发布,全新UI界面,更好的集群 重大 重大 1 Ehcache3 改为 Redis 以便更好的 ...

  6. 红旗 linux界面,红旗Linux桌面系统v11预览版发布:全新UI设计

    原标题:红旗Linux桌面系统v11预览版发布:全新UI设计 关注 来源 | FOSS Lab 如若转载请联系原公众号 近日,国产操作系统红旗 Linux 官网上线最新的红旗 Linux 桌面操作系统 ...

  7. 根植旅游行业酷讯旅游发布2011全新战略

    本文讲的是根植旅游行业酷讯旅游发布2011全新战略,2011年1月11日,领先的在线旅游媒体和专业的旅游搜索引擎酷讯旅游在北京举行战略发布会,宣布2011年全新战略部署.这是酷讯旅游加入全球最大的在线 ...

  8. 2022 Apple 春季发布会带来全新 27 英寸 Studio Display

    北京时间2022年3月9日凌晨的苹果春季发布会上,Apple为大家带来了新的显示器,Studio Display 是 Apple 十多年来推出的第一款以消费者为中心的显示器,旨在与新的 M1 驱动的 ...

  9. 鸿蒙系统发布会是什么时候,鸿蒙系统2.0发布时间是什么时候?或将与EMUI11一同发布!...

    对于鸿蒙系统OS一直以来就备受大家的关注,作为华为自主研发的操作系统,它是华为之光!很多人翘首盼望着它的到来,自1.0版本后鸿蒙系统2.0发布时间似乎确定下来了!届时会与EMUI11一同向大家介绍! ...

最新文章

  1. TCP/IP 计算机网络协议
  2. ARC在Release与Debug模式中内存释放的坑
  3. java groovy jar包_如何将jar包包含在groovy脚本中?
  4. [YTU]_2446( C++习题 输入输出--私有继承)
  5. 人们通常先在线性表尾部临时添加一个_数据结构学习笔记-线性表
  6. AI:2020年6月22日北京智源大会演讲分享之09:00-09:50 全体大会《AI精度与隐私的博弈》
  7. 【研发管理】中国企业 VS 世界优秀企业在产品研发上差距(上)
  8. GDCM:提取JP2文件所有解析度的测试程序
  9. 高考成绩接近满分,却被清华北大拒绝,被称“中国最帅科学家”
  10. JQUERY 使用键盘左右键切换选项卡
  11. SetWindowLong 和SetClassLong区别
  12. 大数据项目之dmp用户画像
  13. ubuntu安装tim
  14. 如何使用FlashgameMaster修改游戏
  15. VMware16安装Win11虚拟机(最全步骤+踩坑)
  16. [渝粤教育] 西南科技大学 中国传统文化概论 在线考试复习资料2021版
  17. 银行案例分析:识别个人贷款客户画像,实现精准营销与风险防范
  18. JS--实现漂浮广告
  19. 本地代码怎么上传到码云?
  20. position中的absolute与relative的区别

热门文章

  1. 腾达W303R v3 无线路由器使用移动光纤无法打开视频的设置方法
  2. 2016年4月11日作业(法律法规、标准规范、职业道德)
  3. 通过git和Xcode将代码上传到GitHub
  4. ASP.NET(C#) 四舍五入、进一法、舍位(取整,舍去小数,向负无穷舍入)函数
  5. 初学者的编程自学指南
  6. 天池实验室|读取数据集的两种方式
  7. jqgrid本地数据例子_微型数据转换器如何通过更小尺寸为您带来更多价值
  8. R eentrantLock的源码分析
  9. 并发的发展历史-线程的出现
  10. MyBatis 与Spring 整合分析