JAVA虚拟机发展

一、java发展史

1995年5月23日,Oak语言改名为Java,并且在SunWorld大会上正式发布Java 1.0版本。Java语言第 一次提出了“Write Once,Run Anywhere”的口号。

1996年1月23日,JDK 1.0发布,Java语言有了第一个正式版本的运行环境。JDK 1.0提供了一个纯 解释执行的Java虚拟机实现(Sun Classic VM)。JDK 1.0版本的代表技术包括:Java虚拟机、Applet、 AWT等。

1996年4月,十个最主要的操作系统和计算机供应商声明将在其产品中嵌入Java技术。同年9月, 已有大约8.3万个网页应用了Java技术来制作。

在1996年5月底,Sun于美国旧金山举行了首届JavaOne大 会,从此JavaOne成为全世界数百万Java语言开发者每年一度的技术盛会。

1997年2月19日,Sun公司发布了JDK 1.1,Java里许多最基础的技术支撑点(如JDBC等)都是在 JDK 1.1版本中提出的,JDK 1.1版的技术代表有:JAR文件格式、JDBC、JavaBeans、RMI等。Java语 言的语法也有了一定的增强,如内部类(Inner Class)和反射(Reflection)都是在这时候出现的。

1998年12月4日,JDK迎来了一个里程碑式的重要版本:工程代号为Playground(竞技场)的JDK 1.2,Sun在这个版本中把Java技术体系拆分为三个方向,分别是面向桌面应用开发的J2SE(Java 2 Platform,Standard Edition)、面向企业级开发的J2EE(Java 2 Platform,Enterprise Edition)和面向手 机等移动终端开发的J2ME(Java 2 Platform,Micro Edition)。在这个版本中出现的代表性技术非常 多,如EJB、Java Plug-in、Java IDL、Swing等,并且这个版本中Java虚拟机第一次内置了JIT(Just In Time)即时编译器(JDK 1.2中曾并存过三个虚拟机,Classic VM、HotSpot VM和Exact VM,其中 Exact VM只在Solaris平台出现过;后面两款虚拟机都是内置了JIT即时编译器的,而之前版本所带的 Classic VM只能以外挂的形式使用即时编译器)。在语言和API层面上,Java添加了strictfp关键字, Java类库添加了现在Java编码之中极为常用的一系列Collections集合类等。在1999年3月和7月,分别有 JDK 1.2.1和JDK 1.2.2两个小升级版本发布。

1999年4月27日,HotSpot虚拟机诞生。HotSpot最初由一家名为“Longview Techno-logies”的小公司 开发,由于HotSpot的优异表现,这家公司在1997年被Sun公司收购。Hot-Spot虚拟机刚发布时是作为 JDK 1.2的附加程序提供的,后来它成为JDK 1.3及之后所有JDK版本的默认Java虚拟机。

2000年5月8日,工程代号为Kestrel(美洲红隼)的JDK 1.3发布。相对于JDK 1.2,JDK 1.3的改进 主要体现在Java类库上(如数学运算和新的Timer API等),JNDI服务从JDK 1.3开始被作为一项平台 级服务提供(以前JNDI仅仅是一项扩展服务),使用CORBA IIOP来实现RMI的通信协议,等等。这 个版本还对Java 2D做了很多改进,提供了大量新的Java 2D API,并且新添加了JavaSound类库。JDK 1.3有1个修正版本JDK 1.3.1,工程代号为Ladybird(瓢虫),于2001年5月17日发布。

2002年2月13日,JDK 1.4发布,工程代号为Merlin(灰背隼)。JDK 1.4是标志着Java真正走向成 熟的一个版本,Compaq、Fujitsu、SAS、Symbian、IBM等著名公司都有参与功能规划,甚至实现自己 独立发行的JDK 1.4。哪怕是在近二十年后的今天,仍然有一些主流应用能直接运行在JDK 1.4之上, 或者继续发布能运行在1.4上的版本。JDK 1.4同样带来了很多新的技术特性,如正则表达式、异常 链、NIO、日志类、XML解析器和XSLT转换器,等等。JDK 1.4有两个后续修正版:2002年9月16日发 布的工程代号为Grasshopper(蚱蜢)的JDK 1.4.1与2003年6月26日发布的工程代号为Mantis(螳螂) 的JDK 1.4.2。

2004年9月30日,JDK 5发布,工程代号为Tiger(老虎)。Sun公司从这个版本开始放弃了谦逊 的“JDK 1.x”的命名方式,将产品版本号修改成了“JDK x” [。从JDK 1.2以来,Java在语法层面上的变 动一直很小,而JDK 5在Java语法易用性上做出了非常大的改进。如:自动装箱、泛型、动态注解、枚 举、可变长参数、遍历循环(foreach循环)等语法特性都是在JDK 5中加入的。在虚拟机和API层面上,这个版本改进了Java的内存模型(Java Memory Model,JMM)、提供了java.util.concurrent并发包 等。另外,JDK 5是官方声明可以支持Windows 9x操作系统的最后一个JDK版本。

2006年12月11日,JDK 6发布,工程代号为Mustang(野马)。在这个版本中,Sun公司终结了从 JDK 1.2开始已经有八年历史的J2EE、J2SE、J2ME的产品线命名方式,启用Java EE 6、Java SE 6、Java ME 6的新命名来代替。JDK 6的改进包括:提供初步的动态语言支持(通过内置Mozilla JavaScript Rhino引擎实现)、提供编译期注解处理器和微型HTTP服务器API,等等。同时,这个版本对Java虚拟 机内部做了大量改进,包括锁与同步、垃圾收集、类加载等方面的实现都有相当多的改动。

2009年4月20日,Oracle宣布正式以74亿美元的价格收购市值曾超过2000亿美元的Sun公司

2011年7月28日   JDK 7包含的改进有:提供新的G1收 集器(G1在发布时依然处于Experimental状态,直至2012年4月的Update 4中才正式商用)、加强对非 Java语言的调用支持(JSR-292,这项特性在到JDK 11还有改动)、可并行的类加载架构等。

2014年3月18日  JDK 8的第一个正式版本,从JDK 8开始,Oracle启用JEP(JDK Enhancement Proposals)来 定义和管理纳入新版JDK发布范围的功能特性。·JEP 126:对Lambda表达式的支持,这让Java语言拥有了流畅的函数式表达能力。 ·JEP 104:内置Nashorn JavaScript引擎的支持。 ·JEP 150:新的时间、日期API。 ·JEP 122:彻底移除HotSpot的永久代。

2017年9月21日 JDK9  Jigsaw外,JDK 9还增强了若干工具(JS Shell、JLink、JHSDB等),整顿了 HotSpot各个模块各自为战的日志系统,支持HTTP 2客户单API等91个JEP。

JDK 9发布后,Oracle随即宣布Java将会以持续交付的形式和更加敏捷的研发节奏向前推进,以后 JDK将会在每年的3月和9月各发布一个大版本[11],目的就是为避免众多功能特性被集中捆绑到一个 JDK版本上而引发交付风险。这次改革确实从根源上解决了跳票问题,但也为Java的用户和发行商带 来了颇大的压力,不仅程序员感慨“Java新版本还没开始用就已经过时了”,Oracle自己对着一堆JDK版 本分支也在挠头,不知道该如何维护更新,该如何提供技术支持。Oracle的解决方案是顺理成章地终 结掉“每个JDK版本最少维护三年”的优良传统,从此以后,每六个JDK大版本中才会被划出一个长期 支持(Long Term Support,LTS)版,只有LTS版的JDK能够获得为期三年的支持和更新,普通版的 JDK就只有短短六个月的生命周期。JDK 8和JDK 11会是LTS版,再下一个就到2021年发布的JDK 17 了。

2018年3月20日,JDK 10如期发布,这版本的主要研发目标是内部重构,诸如统一源仓库、统一 垃圾收集器接口、统一即时编译器接口(JVMCI在JDK 9已经有了,这里是引入新的Graal即时编译 器)等,这些都将会是对未来Java发展大有裨益的改进,但对普通用户来说JDK 10的新特性就显得乏 善可陈,毕竟它只包含了12个JEP,而且其中只有本地类型推断这一个编码端可见的改进。

2018年3月27日,Android的Java侵权案有了最终判决,法庭裁定Google赔偿Oracle合计88亿 美元

2018年10月,JavaOne 2018在旧金山举行,此前没有人想过这会是最后一届JavaOne大会,这个在 1996年伴随着Java一同诞生、成长的开发者年度盛会,竟是Oracle下一个裁撤的对象[15],此外还有 Java Mission Control的开发团队,也在2018年6月被Oracle解散。

2018年9月25日,JDK 11发布,这是一个LTS版本的JDK,包含17个JEP,其中有ZGC这样的革命 性的垃圾收集器出现,也有把JDK 10中的类型推断加入Lambda语法这种可见的改进

2019年2月,在JDK 12发布前夕,Oracle果然如之前宣布那样在六个月之后就放弃了对上一个版本 OpenJDK的维护,RedHat同时从Oracle手上接过OpenJDK 8和OpenJDK 11的管理权利和维护职责

2019年3月20日,JDK 12发布,只包含8个JEP,其中主要有Switch表达式、Java微测试套件 (JMH)等新功能,最引人注目的特性无疑是加入了由RedHat领导开发的Shen-andoah垃圾收集器。 Shenandoah作为首个由非Oracle开发的垃圾收集器,其目标又与Oracle在JDK 11中发布的ZGC几乎完全 一致,两者天生就存在竞争。Oracle马上用实际行动抵制了这个新收集器,在JDK 11发布时才说应尽 可能保证OracleJDK和OpenJDK的兼容一致,转眼就在OracleJDK 12里把Shenandoah的代码通过条件编 译强行剔除掉,使其成为历史上唯一进入了OpenJDK发布清单,但在OracleJDK中无法使用的功能。

二、java虚拟机发展

虚拟机始祖:Sun Classic/Exact VM

以今天的视角来看,Sun Classic虚拟机的技术已经相当原始,这款虚拟机的使命也早已终结。但 仅凭它“世界上第一款商用Java虚拟机”的头衔,就足够有令历史记住它的理由。

1996年1月23日,Sun发布JDK 1.0,Java语言首次拥有了商用的正式运行环境,这个JDK中所带的 虚拟机就是Classic VM。这款虚拟机只能使用纯解释器方式来执行Java代码,如果要使用即时编译器那 就必须进行外挂,但是假如外挂了即时编译器的话,即时编译器就会完全接管虚拟机的执行系统,解 释器便不能再工作了。

武林盟主:HotSpot VM

HotSpot虚拟机,它是Sun/OracleJDK和OpenJDK中的默认Java虚拟 机,也是目前使用范围最广的Java虚拟机。但不一定所有人都知道的是,这个在今天看起来“血统纯 正”的虚拟机在最初并非由Sun公司所开发,而是由一家名为“Longview Technologies”的小公司设计;甚 至这个虚拟机最初并非是为Java语言而研发的,它来源于Strongtalk虚拟机,而这款虚拟机中相当多的 技术又是来源于一款为支持Self语言实现“达到C语言50%以上的执行效率”的目标而设计的Self虚拟机, 最终甚至可以追溯到20世纪80年代中期开发的Berkeley Smalltalk上。Sun公司注意到这款虚拟机在即时 编译等多个方面有着优秀的理念和实际成果,在1997年收购了Longview Technologies公司,从而获得了 HotSpot虚拟机。

HotSpot既继承了Sun之前两款商用虚拟机的优点(如前面提到的准确式内存管理),也有许多自 己新的技术优势,如它名称中的HotSpot指的就是它的热点代码探测技术

Oracle收购Sun以后,建立了HotRockit项目来把原来BEA JRockit中的优秀特性融合到 HotSpot之中。到了2014年的JDK 8时期,里面的HotSpot就已是两者融合的结果,HotSpot在这个过程 里移除掉永久代,吸收了JRockit的Java Mission Control监控工具等功能。

小家碧玉:Mobile/Embedded VM

Sun/Oracle公司所研发的虚拟机可不仅包含前面介绍到的服务器、桌面领域的商用虚拟机,面对移 动和嵌入式市场,也有专门的Java虚拟机产品。

由于Java ME产品线的发展相对Java SE来说并不那么成功,所以Java ME中的Java虚拟机相比 HotSpot要低调得多。Oracle公司在Java ME这条产品线上的虚拟机名为CDC-HI(C Virtual Machine, CVM)和CLDC-HI(Monty VM)。其中CDC/CLDC全称是Connected(Limited)Device Configuration,这是一组在JSR-139及JSR-218规范中进行定义的Java API子集,这组规范希望能够在手 机、电子书、PDA等移动设备上建立统一的Java编程接口,CDC-HI VM和CLDC-HI VM就是JSR-139 及JSR-218规范的参考实现,后面的HI则是HotSpot Implementation的缩写,但它们并不是由HotSpot直 接裁剪而来,只是借鉴过其中一些技术,并没有血缘关系,充其量能叫有所渊源。

Java ME Embedded

目前CLDC中活得最好的产品反而是原本早该被CLDC-HI淘汰的 KVM,国内的老人手机和出口到经济欠发达国家的功能手机(Feature Phone)还在广泛使用这种更加 简单、资源消耗也更小的上一代Java ME虚拟机。

天下第二:BEA JRockit/IBM J9 VM

历史上除了Sun/Oracle公司以外,也有其 他组织、公司开发过虚拟机的实现。如果说HotSpot是天下第一的武林盟主,那曾经与HotSpot并称“三 大商业Java虚拟机”的另外两位,毫无疑问就该是天下第二了,它们分别是BEA System公司的JRockit与 IBM公司的IBM J9。

软硬合璧:BEA Liquid VM/Azul VM

我们平时所提及的“高性能Java虚拟机”一般是指HotSpot、JRockit、J9这类在通用硬件平台上运行 的商用虚拟机,但其实还有一类与特定硬件平台绑定、软硬件配合工作的专有虚拟机,往往能够实现 更高的执行性能,或提供某些特殊的功能特性。这类专有虚拟机的代表是BEA Liquid VM和Azul VM。

Liquid VM也被称为JRockit VE(Virtual Edition,VE),它是BEA公司开发的可以直接运行在自家 Hypervisor系统上的JRockit虚拟机的虚拟化版本,Liquid VM不需要操作系统的支持,或者说它自己本 身实现了一个专用操作系统的必要功能,如线程调度、文件系统、网络支持等。由虚拟机越过通用操 作系统直接控制硬件可以获得很多好处,如在线程调度时,不需要再进行内核态/用户态的切换,这样 可以最大限度地发挥硬件的能力,提升Java程序的执行性能。随着JRockit虚拟机终止开发,Liquid VM 项目也已经停止了。

Azul VM是Azul Systems公司在HotSpot基础上进行大量改进,运行于Azul Systems公司的专有硬 件Vega系统上的Java虚拟机,每个Azul VM实例都可以管理至少数十个CPU和数百GB的内存的硬件资 源,并提供在巨大内存范围内停顿时间可控的垃圾收集器(即业内赫赫有名的PGC和C4收集器),为 专有硬件优化的线程调度等优秀特性。2010年起,Azul公司的重心逐渐开始从硬件转向软件,发布了 自己的Zing虚拟机,可以在通用x86平台上提供接近于Vega系统的性能和一致的功能特性。

Zing虚拟机是一个从HotSpot某旧版代码分支基础上独立出来重新开发的高性能Java虚拟机,它可 以运行在通用的Linux/x86-64平台上。Azul公司为它编写了新的垃圾收集器,也修改了HotSpot内的许 多实现细节,在要求低延迟、快速预热等场景中,Zing VM都要比HotSpot表现得更好。Zing的PGC、 C4收集器可以轻易支持TB级别的Java堆内存,而且保证暂停时间仍然可以维持在不超过10毫秒的范围 里,HotSpot要一直到JDK 11和JDK 12的ZGC及Shenandoah收集器才达到了相同的目标,而且目前效 果仍然远不如C4。Zing的ReadyNow!功能可以利用之前运行时收集到的性能监控数据,引导虚拟机 在启动后快速达到稳定的高性能水平,减少启动后从解释执行到即时编译的等待时间。Zing自带的 ZVision/ZVRobot功能可以方便用户监控Java虚拟机的运行状态,从找出代码热点到对象分配监控、锁 竞争监控等。Zing能让普通用户无须了解垃圾收集等底层调优,就可以使得Java应用享有低延迟、快 速预热、易于监控的功能,这是Zing的核心价值和卖点,很多Java应用都可以通过长期努力在应用、 框架层面优化来提升性能,但使用Zing的话就可以把精力更多集中在业务方面。

挑战者:Apache Harmony/Google Android Dalvik VM

Harmony虚拟机(准确地说是Harmony里的DRLVM)和Dalvik虚拟机只能称作“虚拟 机”,而不能称作“Java虚拟机”,但是这两款虚拟机以及背后所代表的技术体系曾经对Java世界产生了 非常大的影响和挑战,当时甚至有悲观的人认为成熟的Java生态系统都有分裂和崩溃的可能。

Dalvik虚拟机曾经是Android平台的核心组成部分之一,它的名字来源于冰岛一个名为Dalvik的小 渔村。Dalvik虚拟机并不是一个Java虚拟机,它没有遵循《Java虚拟机规范》,不能直接执行Java的 Class文件,使用寄存器架构而不是Java虚拟机中常见的栈架构。但是它与Java却又有着千丝万缕的联 系,它执行的DEX(Dalvik Executable)文件可以通过Class文件转化而来,使用Java语法编写应用程 序,可以直接使用绝大部分的Java API等。在Android发展的早期,Dalvik虚拟机随着Android的成功迅 速流行,在Android 2.2中开始提供即时编译器实现,执行性能又有了进一步提高。不过到了Android 4.4时代,支持提前编译(Ahead of Time Compilation,AOT)的ART虚拟机迅速崛起,在当时性能还 不算特别强大的移动设备上,提前编译要比即时编译更容易获得高性能,所以在Android 5.0里ART就 全面代替了Dalvik虚拟机。

没有成功,但并非失败:Microsoft JVM及其他

在Java语言诞生的初期(1996年~1998年,以JDK1.2发布之前为分界),它的主要应用之一是在 浏览器中运行Java Applets程序,微软为了在Internet Explorer 3浏览器中支持Java Applets应用而开发了 自己的Java虚拟机,虽然这款虚拟机只有Windows平台的版本,“一次编译,到处运行”根本无从谈起, 但却是当时Windows系统下性能最好的Java虚拟机,它在1997年和1998年连续获得了《PC Magazine》 杂志的“编辑选择奖”。但是好景不长,在1997年10月,Sun公司正式以侵犯商标、不正当竞争等罪名控 告微软,在随后对微软公司的垄断调查之中,这款虚拟机也曾作为证据之一被呈送法庭。官司的结果 是微软向Sun公司(最终微软因垄断赔偿给Sun公司的总金额高达10亿美元)赔偿2000万美金,承诺终 止其Java虚拟机的发展,并逐步在产品中移除Java虚拟机相关功能。

百家争鸣

还有一些Java虚拟机天生就注定不会应用在主流领域,或者不是单纯为了用于生产,甚至在设计 之初就没有抱着商用的目的,仅仅是用于研究、验证某种技术和观点,又或者是作为一些规范的标准 实现。这些虚拟机对于大多数不从事相关领域开发的Java程序员来说可能比较陌生,笔者列举几款较 为有影响的:

·KVM[1] KVM中的K是“Kilobyte”的意思,它强调简单、轻量、高度可移植,但是运行速度比较慢。在 Android、iOS等智能手机操作系统出现前曾经在手机平台上得到非常广泛应用。

·Java Card VM JCVM是Java虚拟机很小的一个子集,裁减了许多模块但通常支持绝大多数的常用加密算法。 JCVM必须精简到能放入智能卡、SIM卡、银行信用卡、借记卡内,负责对Java Applet程序进行解释执 行。 ·Squawk VM Squawk VM是由Sun开发,运行于Sun SPOT(Sun Small Programmable Object Tech-nology,一种手 持的Wi-Fi设备),也曾经运用于Java Card。这是一个Java代码比重很高的嵌入式虚拟机实现,其中诸 如类加载器、字节码验证器、垃圾收集器、解释器、编译器和线程调度都是用Java语言完成的,仅仅 靠C语言来编写设备I/O和必要的本地代码。

·JavaInJava JavaInJava是Sun公司在1997年~1998年间所研发的一个实验室性质的虚拟机,从名字就可以看 出,它试图以Java语言来实现Java语言本身的运行环境,既所谓的“元循环”(Meta-Circular,是指使用 语言自身来实现其运行环境)虚拟机。它必须运行在另外一个宿主虚拟机之上,内部没有即时编译 器,代码只能以解释模式执行。在上世纪末主流原生的Java虚拟机都未能很好解决性能问题的时代, 开发这种项目,其执行速度大家可想而知,不过通过元循环证明一门语言可以自举,是具有它的研究 价值的。

·Maxine VM Maxine VM和上面的JavaInJava非常相似,它也是一个几乎全部以Java代码实现(只有用于启动 Java虚拟机的加载器使用C语言编写)的元循环Java虚拟机。这个项目于2005年开始,到现在仍然在发 展之中,比起JavaInJava,Maxine VM的执行效率就显得靠谱得多,它有先进的即时编译器和垃圾收集 器,可在宿主模式或独立模式下执行,其执行效率已经接近HotSpot虚拟机Client模式的水平。后来有 了从C1X编译器演进而来的Graal编译器的支持,就更加如虎添翼,执行效率有了进一步飞跃。Graal编 译器现在已经是HotSpot的默认组件,是未来代替HotSpot中服务端编译器的希望。

·Jikes RVM Jikes RVM是IBM开发的专门用来研究Java虚拟机实现技术的项目。曾用名为Jalapeño。与 JavaInJava和Maxine一样,它也是一个元循环虚拟机。

·IKVM.NET 这是一个基于微软.NET框架实现的Java虚拟机,并借助Mono获得一定的跨平台能力。IKVM.NET 的目标第一眼看起来的确很奇怪,可能在某些特殊情况下,在.NET上使用某些流行的Java库也许真的 不算是伪需求?IKVM.NET可以将Class文件编译成.NET Assembly,在任意的CLI上运行。

三、java技术未来

无语言倾向

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可以无额外开销地混合使用这些编程语言, 支持不同语言中混用对方的接口和对象,也能够支持这些语言使用已经编写好的本地库文件。

新一代即时编译器

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)的 策略,支持自定义的预测性假设等。

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

向Native迈进

在微服务架构的视角下,应用拆分后,单个微服务很可能就不再需要面对数十、数百GB乃至TB 的内存,有了高可用的服务集群,也无须追求单个服务要7×24小时不间断地运行,它们随时可以中断 和更新;但相应地,Java的启动时间相对较长,需要预热才能达到最高性能等特点就显得相悖于这样 的应用场景。在无服务架构中,矛盾则可能会更加突出,比起服务,一个函数的规模通常会更小,执 行时间会更短,当前最热门的无服务运行环境AWS Lambda所允许的最长运行时间仅有15分钟。

一直把软件服务作为重点领域的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用来支持提前编译后的程序执行。 它还包含了一个本地镜像的构造器(Native Image Generator),用于为用户程序建立基于Substrate VM 的本地运行时镜像。这个构造器采用指针分析(Points-To Analysis)技术,从用户提供的程序入口出 发,搜索所有可达的代码。在搜索的同时,它还将执行初始化代码,并在最终生成可执行文件时,将 已初始化的堆保存至一个堆快照之中。这样一来,Substrate VM就可以直接从目标程序开始运行,而 无须重复进行Java虚拟机的初始化过程。但相应地,原理上也决定了Substrate VM必须要求目标程序是 完全封闭的,即不能动态加载其他编译器不可知的代码和类库。基于这个假设,Substrate VM才能探索整个编译空间,并通过静态分析推算出所有虚方法调用的目标方法。

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

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

灵活的胖子

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

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

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

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

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

语言语法持续增强

JDK 7的Coins项目结束以后,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线程的调度是直接由操作系统 内核提供的(这方面的内容可见本书第12章),会有核心态、用户态的切换开销。而很多其他语言都 提供了更加轻量级的、由软件自身进行调度的用户线程(曾经非常早期的Java也有绿色线程),譬如 Golang的Groutine、D语言的Fiber等。Loom项目就准备提供一套与目前Thread类API非常接近的Fiber实 现。

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

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

实战:自己编译JDK

想要窥探Java虚拟机内部的实现原理,最直接的一条路径就是编译一套自己的JDK,通过阅读和 跟踪调试JDK源码来了解Java技术体系的运作,虽然这样门槛会比阅读资料更高一点,但肯定也会比 阅读各种文章、书籍来得更加贴近本质。此外,Java类库里的很多底层方法都是Native的,在了解这些 方法的运作过程,或对JDK进行Hack(根据需要进行定制微调)的时候,都需要有能自行编译、调试 虚拟机代码的能力。

选择OpenJDK来进行这次编译实战。

获取源码

到了JDK 10及以后的版本,在组织上出现了一些新变化,此时全部开发工作统一归属到JDK和 JDK Updates两条主分支上,主分支不再带版本号,在内部再用子分支来区分具体的JDK版本。 OpenJDK不同版本的源码都可以在它们的主页(http://openjdk.java.net/)上找到

获取OpenJDK源码有两种方式。一是通过Mercurial代码版本管理工具从Repository中直接取得源 码(Repository地址:https://hg.openjdk.java.net/jdk/jdk12),获取过程如以下命令所示:

hg clone https://hg.openjdk.java.net/jdk/jdk12

这是直接取得OpenJDK源码的方式,从版本管理中看变更轨迹也能够更精确地了解到Java代码发 生的变化,但弊端是在中国访问的速度实在太慢,虽然代码总量只有几百MB,无奈文件数量将近十 万,而且仓库没有国内的CDN节点。以笔者的网络状况,不科学上网的话,全部复制到本地需要耗费 数小时时间。另外,考虑到Mercurial远不如Git常用,甚至普及程度还不如SVN、ClearCase以及更古老 的CVS等版本控制工具,对于大多数读者,笔者建议采用第二种方式,即直接在仓库中打包出源码压 缩包,再进行下载。

读者可以直接访问准备下载的JDK版本的仓库页面(譬如本例中OpenJDK 12的页面 为https://hg.openjdk.java.net/jdk/jdk12/),然后点击左边菜单中的“Browse”,将显示如图1-9的源码根目 录页面。

此时点击左边的“zip”链接即可下载当前版本打包好的源码,到本地直接解压即可。在国内使用这 种方式下载比起从Mercurial复制一堆零散文件要快非常多。笔者下载的OpenJDK 12源码包大小为 171MB,解压之后约为579MB。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值