(三)展望Java技术的未来

本篇主要简单 了解一下Java技术的未来,结合着【深入理解Java虚拟机】一书当中整理了本篇博客,感兴趣的可以跟着博主学习一下!

一、Graal VM

网上每隔一段时间就能见到几条“未来X语言将会取代Java”的新闻,此处“X”可以用Kotlin、 Golang、Dart、JavaScript、Python等各种编程语言来代入。这大概就是长期占据编程语言榜单第一 位的烦恼,天下第一总避免不了挑战者相伴。

2018年4月,Oracle Labs新公开了一项黑科技:Graal VM,如图1-4所示,从它的口号“Run Programs Faster Anywhere”(中文:在任何地方运行程序都更快)就能感觉到一颗蓬勃的野心,这句话显然是与1995年Java刚诞生时的“Write Once,Run Anywhere”在遥相呼应。

Graal VM被官方称为“Universal VM”和“Polyglot VM”,这是一个在HotSpot虚拟机基础上增强而成 的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用,这里“任何语言”包括了Java、Scala、 Groovy、Kotlin等基于Java虚拟机之上的语言,还包括了C、C++、Rust等基于LLVM的语言,同时支 持其他像JavaScript、Ruby、Python和R语言等。Graal VM可以无额外开销地混合使用这些编程语言, 支持不同语言中混用对方的接口和对象,也能够支持这些语言使用已经编写好的本地库文件。

Graal VM的基本工作原理是将这些语言的源代码(例如JavaScript)或源代码编译后的中间格式 (例如LLVM字节码)通过解释器转换为能被Graal VM接受的中间表示(Intermediate Representation, IR),譬如设计一个解释器专门对LLVM输出的字节码进行转换来支持C和C++语言,这个过程称为程 序特化(Specialized,也常被称为Partial Evaluation)。Graal VM提供了Truffle工具集来快速构建面向一 种新语言的解释器,并用它构建了一个称为Sulong的高性能LLVM字节码解释器。

从更严格的角度来看,Graal VM才是真正意义上与物理计算机相对应的高级语言虚拟机,理由是 它与物理硬件的指令集一样,做到了只与机器特性相关而不与某种高级语言特性相关。Oracle Labs的 研究总监Thomas Wuerthinger在接受InfoQ采访时谈到:“随着GraalVM 1.0的发布,我们已经证明了拥 有高性能的多语言虚拟机是可能的,并且实现这个目标的最佳方式不是通过类似Java虚拟机和微软 CLR那样带有语言特性的字节码。”对于一些本来就不以速度见长的语言运行环境,由于Graal VM 本身能够对输入的中间表示进行自动优化,在运行时还能进行即时编译优化,因此使用Graal VM实现 往往能够获得比原生编译器更优秀的执行效率,譬如Graal.js要优于Node.js[3],Graal.Python要优于 CPtyhon[4],TruffleRuby要优于Ruby MRI,FastR要优于R语言等。

对Java而言,Graal VM本来就是在HotSpot基础上诞生的,天生就可作为一套完整的符合Java SE 8 标准的Java虚拟机来使用。它和标准的HotSpot的差异主要在即时编译器上,其执行效率、编译质量目 前与标准版的HotSpot相比也是互有胜负。但现在Oracle Labs和美国大学里面的研究院所做的最新即时 编译技术的研究全部都迁移至基于Graal VM之上进行了,其发展潜力令人期待。如果Java语言或者 HotSpot虚拟机真的有被取代的一天,那从现在看来Graal VM是希望最大的一个候选项,这场革命很
可能会在Java使用者没有明显感觉的情况下悄然而来,Java世界所有的软件生态都没有发生丝毫变化, 但天下第一的位置已经悄然更迭。

二、新一代即时编译器

对需要长时间运行的应用来说,由于经过充分预热,热点代码会被HotSpot的探测机制准确定位捕 获,并将其编译为物理硬件可直接执行的机器码,在这类应用中Java的运行效率很大程度上取决于即 时编译器所输出的代码质量

HotSpot虚拟机中含有两个即时编译器,分别是 编译耗时短输出代码优化程度较低 的客户端编译 器(简称为C1)以及编译耗时长但输出代码优化质量也更高的服务端编译器(简称为C2),通常它们 会在分层编译机制下与解释器互相配合来共同构成HotSpot虚拟机的执行子系统

JDK 10起,HotSpot中又加入了一个全新的即时编译器Graal编译器,看名字就可以联想到它 是来自于前一节提到的Graal VM。Graal编译器是以C2编译器替代者的身份登场的。C2的历史已经非 常长了,可以追溯到Cliff Click大神读博士期间的作品,这个由C++写成的编译器尽管目前依然效果拔 群,但已经复杂到连Cliff Click本人都不愿意继续维护的程度。而Graal编译器本身就是由Java语言写 成,实现时又刻意与C2采用了同一种名为“Sea-of-Nodes”的高级中间表示(High IR)形式,使其能够 更容易借鉴C2的优点。Graal编译器比C2编译器晚了足足二十年面世,有着极其充沛的后发优势,在保 持输出相近质量的编译代码的同时,开发效率和扩展性上都要显著优于C2编译器,这决定了C2编译器 中优秀的代码优化技术可以轻易地移植到Graal编译器上,但是反过来Graal编译器中行之有效的优化在 C2编译器里实现起来则异常艰难。这种情况下,Graal的编译效果短短几年间迅速追平了C2,甚至某些 测试项中开始逐渐反超C2编译器。Graal能够做比C2更加复杂的优化,如“部分逃逸分析”(Partial Escape Analysis),也拥有比C2更容易使用激进预测性优化(Aggressive Speculative Optimization)的 策略,支持自定义的预测性假设等。

今天的Graal编译器尚且年幼,还未经过足够多的实践验证,所以仍然带着“实验状态”的标签,需 要用开关参数去激活,这让笔者不禁联想起JDK 1.3时代,HotSpot虚拟机刚刚横空出世时的场景, 同样也是需要用开关激活,也是作为Classic虚拟机的替代品的一段历史。

Graal编译器未来的前途可期,作为Java虚拟机执行代码的最新引擎,它的持续改进,会同时为 HotSpot与Graal VM注入更快更强的驱动力。

使用-XX:+UnlockExperimentalVMOptions-XX:+UseJVMCICompiler参数来启用Graal编译器。

三、向Native迈进

对不需要长时间运行的,或者小型化的应用而言,Java天生就带有一些劣 势,这里并不只是指跑个HelloWorld也需要百多兆的JRE之类的问题,更重要的是指近几年在从大型单 体应用架构向小型微服务应用架构发展的技术潮流下,Java表现出来的不适应。

在微服务架构的视角下,应用拆分后,单个微服务很可能就不再需要面对数十、数百GB乃至TB 的内存,有了高可用的服务集群也无须追求单个服务要7×24小时不间断地运行它们随时可以中断 和更新;但相应地,Java的启动时间相对较长,需要预热才能达到最高性能等特点就显得相悖于这样 的应用场景。

一直把软件服务作为重点领域的Java自然不可能对此视而不见,在最新的几个JDK版本的功能清 单中,已经陆续推出了跨进程的、可以面向用户程序的类型信息共享(Application Class Data Sharing,AppCDS,允许把加载解析后的类型信息缓存起来,从而提升下次启动速度,原本CDS只支 持Java标准库,在JDK 10时的AppCDS开始支持用户的程序代码)、无操作的垃圾收集器(Epsilon, 只做内存分配而不做回收的收集器,对于运行完就退出的应用十分合适)等改善措施。而酝酿中的一 个更彻底的解决方案,是逐步开始对提前编译(Ahead of Time Compilation,AOT)提供支持

提前编译是相对于即时编译的概念,提前编译能带来的最大好处是Java虚拟机加载这些已经预编 译成二进制库之后就能够直接调用,而无须再等待即时编译器在运行时将其编译成二进制机器码。理 论上,提前编译可以减少即时编译带来的预热时间,减少Java应用长期给人带来的“第一次运行慢”的 不良体验,可以放心地进行很多全程序的分析行为,可以使用时间压力更大的优化措施。

但是提前编译的坏处也很明显,它破坏了Java“一次编写,到处运行”的承诺,必须为每个不同的 硬件、操作系统去编译对应的发行包;也显著降低了Java链接过程的动态性,必须要求加载的代码在 编译期就是全部已知的,而不能在运行期才确定,否则就只能舍弃掉已经提前编译好的版本,退回到 原来的即时编译执行状态。

早在JDK 9时期,Java就提供了实验性的Jaotc命令来进行提前编译,不过多数人试用过后都颇感失 望,大家原本期望的是类似于Excelsior JET那样的编译过后能生成本地代码完全脱离Java虚拟机运行的 解决方案,但Jaotc其实仅仅是代替即时编译的一部分作用而已,仍需要运行于HotSpot之上

直到Substrate VM出现,才算是满足了人们心中对Java提前编译的全部期待。Substrate VM是在 Graal VM 0.20版本里新出现的一个极小型的运行时环境,包括了独立的异常处理、同步调度、线程管 理、内存管理(垃圾收集)和JNI访问等组件,目标是代替HotSpot用来支持提前编译后的程序执行

Substrate VM带来的好处是能显著降低内存占用及启动时间,由于HotSpot本身就会有一定的内存 消耗(通常约几十MB),这对最低也从几GB内存起步的大型单体应用来说并不算什么,但在微服务 下就是一笔不可忽视的成本。根据Oracle官方给出的测试数据,运行在Substrate VM上的小规模应用, 其内存占用和启动时间与运行在HotSpot上相比有5倍到50倍的下降

Graal VM支持其他语言时不会有重量级的运行负担。譬如运行JavaScript代码,Node.js的V8引擎执行效 率非常高,但即使是最简单的HelloWorld,它也要使用约20MB的内存,而运行在Substrate VM上的 Graal.js,跑一个HelloWorld则只需要4.2MB内存,且运行速度与V8持平。Substrate VM的轻量特性,使 得它十分适合嵌入其他系统,譬如Oracle自家的数据库就已经开始使用这种方式支持用不同的语言代 替PL/SQL来编写存储过程。

四、灵活的胖子

即使HotSpot最初设计时考虑得再长远,大概也不会想到这个虚拟机将在未来的二十年内一直保持 长盛不衰。这二十年间有无数改进和功能被不断地添加到HotSpot的源代码上,致使它成长为今天这样 的庞然大物

HotSpot的定位是面向各种不同应用场景的全功能Java虚拟机,这是一个极高的要求,仿佛是让 一个胖子能拥有敏捷的身手一样的矛盾。如果是持续跟踪近几年OpenJDK的代码变化的人,相信都感 觉到了HotSpot开发团队正在持续地重构着HotSpot的架构,让它具有模块化的能力和足够的开放性。 模块化[2]方面原本是HotSpot的弱项,监控、执行、编译、内存管理等多个子系统的代码相互纠缠。 而IBM的J9就一直做得就非常好,面向Java ME的J9虚拟机与面向Java EE的J9虚拟机可以是完全由同一 套代码库编译出来的产品,只有编译时选择的模块配置有所差别。

现在,HotSpot虚拟机也有了与J9类似的能力,能够在编译时指定一系列特性开关,让编译输出的 HotSpot虚拟机可以裁剪成不同的功能,譬如支持哪些编译器,支持哪些收集器,是否支持JFR、 AOT、CDS、NMT等都可以选择。能够实现这些功能特性的组合拆分,反映到源代码不仅仅是条件编 译,更关键的是接口与实现的分离。

早期(JDK 1.4时代及之前)的HotSpot虚拟机为了提供监控、调试等不会在《Java虚拟机规范》 中约定的内部功能和数据,就曾开放过Java虚拟机信息监控接口(Java Virtual Machine Profiler Interface,JVMPI与Java虚拟机调试接口(Java Virtual Machine Debug Interface,JVMDI供运维和性 能监控、IDE等外部工具使用到了JDK 5时期,又抽象出了层次更高的Java虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI来为所有Java虚拟机相关的工具提供本地编程接口集合,到 JDK 6时JVMTI就完全整合代替了JVMPI和JVMDI的作用。

在JDK 9时期,HotSpot虚拟机开放了Java语言级别的编译器接口(Java Virtual Machine Compiler Interface,JVMCI),使得在Java虚拟机外部增加、替换即时编译器成为可能,这个改进实现起来并不 费劲,但比起之前JVMPI、JVMDI和JVMTI却是更深层次的开放,它为不侵入HotSpot代码而增加或 修改HotSpot虚拟机的固有功能逻辑提供了可行性。Graal编译器就是通过这个接口植入到HotSpot之 中

到了JDK 10,HotSpot又重构了Java虚拟机的垃圾收集器接口(Java Virtual Machine Compiler Interface),统一了其内部各款垃圾收集器的公共行为。有了这个接口,才可能存在日后(今天尚未) 某个版本中的CMS收集器退役,和JDK 12中Shenandoah这样由Oracle以外其他厂商领导开发的垃圾收 集器进入HotSpot中的事情。如果未来这个接口完全开放的话,甚至有可能会出现其他独立于HotSpot 的垃圾收集器实现

经过一系列的重构与开放,HotSpot虚拟机逐渐从时间的侵蚀中挣脱出来,虽然代码复杂度还在增 长,体积仍在变大,但其架构并未老朽,而是拥有了越来越多的开放性和扩展性,使得HotSpot成为一 个能够联动外部功能,能够应对各种场景,能够学会十八般武艺的身手灵活敏捷的“胖子”。

五、语言语法持续增强

笔者将语言的功能特性和语法放到最后来讲,因为它是相对最不重要的改进点,毕竟连JavaScript 这种“反人类”的语法都能获得如此巨大的成功,而比Java语法先进优雅得多的挑战者C#现在已经“江湖 日下”,成了末路英雄。

但一门语言的功能、语法是影响语言生产力和效率的重要因素,很多语言特性和语法糖不论有 没有,程序也照样能写,但即使只是可有可无的语法糖,也是直接影响语言使用者的幸福感程度的关 键指标。JDK 7的Coins项目结束以后(Coin便是Java 7(和Java 8)中一个开源的子项目。创建Coin项目是为了反映Java语言中的微小变动;),Java社区又创建了另外一个新的语言特性改进项目Amber,JDK 10至13里面提供的新语法改进基本都来自于这个项目,譬如:

  • JEP 286:Local-Variable Type Inference,在JDK 10中提供,本地类型变量推断。
  • JEP 323:Local-Variable Syntax for Lambda Parameters,在JDK 11中提供,JEP 286的加强,使它可以用在Lambda中。
  • JEP 325:Switch Expressions,在JDK 13中提供,实现switch语句的表达式支持
  • JEP 335:Text Blocks,在JDK 13中提供,支持文本块功能,可以节省拼接HTML、SQL等场景里 大量的“+”操作。

还有一些是仍然处于草稿状态或者暂未列入发布范围的JEP,可供我们窥探未来Java语法的变化, 譬如:·

  • JEP 301:Enhanced Enums,允许常量类绑定数据类型,携带额外的信息
  • JEP 302:Lambda Leftovers,用下划线来表示Lambda中的匿名参数
  • JEP 305:Pattern Matching for instanceof,用instanceof判断过的类型,在条件分支里面可以不需要 做强类型转换就能直接使用

除语法糖以外,语言的功能也在持续改进之中,以下几个项目是目前比较明确的,也是受到较多 关注的功能改进计划:

  • Project Loom:现在的Java做并发处理的最小调度单位是线程Java线程的调度是直接由操作系统 内核提供的,会有核心态、用户态的切换开销。而很多其他语言都 提供了更加轻量级的、由软件自身进行调度的用户线程(曾经非常早期的Java也有绿色线程),譬如 Golang的Groutine、D语言的Fiber等。Loom项目就准备提供一套与目前Thread类API非常接近的Fiber实 现

  • Project Valhalla:提供值类型和基本类型的泛型支持,并提供明确的不可变类型和非引用类型的声 明。而不可变类型在并发编程中能带来很多好处,没 有数据竞争风险带来了更好的性能。一些语言(如Scala)就有明确的不可变类型声明,而Java中只能 在定义类时将全部字段声明为final来间接实现基本类型的范型支持是指在泛型中引用基本数据类型不需要自动装箱和拆箱,避免性能损耗

  • Project Panama:目的是消弭Java虚拟机与本地代码之间的界线现在Java代码可以通过JNI来调用 本地代码,这点在与硬件交互频繁的场合尤其常用(譬如Android)。但是JNI的调用方式充其量只能 说是达到能用的标准而已,使用起来仍相当烦琐,频繁执行的性能开销也非常高昂,Panama项目的目 标就是提供更好的方式让Java代码与本地代码进行调用和传输数据

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怪 咖@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值