深入理解 Java虚拟机 第1章 走近 Java 2
世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本来就是一个不断追求完美的过 程。
当我们在使用一门技术时,不再依赖书本和他人就能得到这些问题的答案,那才算升华到了“不 惑”的境界。
从广义上讲,Kotlin、Clojure、JRuby、Groovy等运行于Java虚拟机上的编程语言及其相关的程序 都属于Java技术体系中的一员。如果仅从传统意义上来看,JCP官方[1]所定义的Java技术体系包括了以 下几个组成部分:
- ·Java程序设计语言
- ·各种硬件平台上的Java虚拟机实现
- ·Class文件格式
- ·Java类库API
- ·来自商业机构和开源社区的第三方Java类库
我们可以把Java程序设计语言、Java虚拟机、Java类库这三部分统称为JDK(Java Development Kit),JDK是用于支持Java程序开发的最小环境,本书中为行文方便,在不产生歧义的地方常以JDK 来代指整个Java技术体系[2]。可以把Java类库API中的Java SE API子集[3]和Java虚拟机这两部分统称为 JRE(Java Runtime Environment),JRE是支持Java程序运行的标准环境。
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 InTime)即时编译器(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类库。JDK1.3有1个修正版本JDK 1.3.1,工程代号为Ladybird(瓢虫),于2001年5月17日发布。
自从JDK 1.3开始,Sun公司维持着稳定的研发节奏:大约每隔两年发布一个JDK的主版本,以动物命名,期间发布的各个修正版本则以昆虫作为工程代号。
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”[1]。从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、JavaME 6的新命名来代替。JDK 6的改进包括:提供初步的动态语言支持(通过内置Mozilla JavaScriptRhino引擎实现)、提供编译期注解处理器和微型HTTP服务器API,等等。同时,这个版本对Java虚拟机内部做了大量改进,包括锁与同步、垃圾收集、类加载等方面的实现都有相当多的改动。
在2006年11月13日的JavaOne大会上,Sun公司宣布计划要把Java开源,在随后的一年多时间内,它陆续地将JDK的各个部分在GPLv2(GNU General Public License v2)协议下公开了源码,并建立了OpenJDK组织对这些源码进行独立管理。除了极少量的产权代码(Encumbered Code,这部分代码所有权不属于Sun公司,Sun本身也无权进行开源处理)外,OpenJDK几乎拥有了当时SunJDK 7的全部代码,OpenJDK的质量主管曾经表示在JDK 7中,SunJDK和OpenJDK除了代码文件头的版权注释之外,代码几乎是完全一样的,所以OpenJDK 7与SunJDK 7本质上就是同一套代码库出来的产品。
从JDK 7最原始的功能清单来看,它本应是一个包含许多重要改进的JDK版本,其中规划的子项目都为Java业界翘首以盼,包括:
- ·Lambda项目:支持Lambda表达式,支持函数式编程。
- ·Jigsaw项目:虚拟机层面的模块化支持。
- ·动态语言支持:Java是静态语言,为其他运行在Java虚拟机上的动态语言提供支持。
- ·Garbage-First收集器。
- ·Coin项目:Java语法细节进化。
令人惋惜的是,在JDK 7开发期间,Sun公司相继在技术竞争和商业竞争中陷入泥潭,公司的股票市值跌至仅有高峰时期的3%,已无力推动JDK 7的研发工作按计划继续进行。为了尽快结束JDK 7长期跳票的问题,Oracle收购Sun公司后随即宣布马上实行“B计划”,大幅裁剪了JDK 7预定目标,以保证JDK 7的正式版能够于2011年7月28日准时发布。“B计划”的主要措施是把不能按时完成的Lambda项目、Jigsaw项目和Coin项目的部分改进延迟到JDK 8之中。最终,JDK 7包含的改进有:提供新的G1收集器(G1在发布时依然处于Experimental状态,直至2012年4月的Update 4中才正式商用)、加强对非 Java语言的调用支持(JSR-292,这项特性在到JDK 11还有改动)、可并行的类加载架构等。
Oracle公司接手了JDK开发工作以后,迅速展现出了完全不同于Sun时期的、极具商业化的处事风格。面对Java中使用最广泛而又一直免费的Java SE产品线,Oracle很快定义了一套新的Java SESupport[2]产品计划,把JDK的更新支持作为一项商业服务。JDK 7发布的前80个更新仍然免费面向所有用户提供,但后续的其他更新包,用户[3]只能从“将Java SE升级到Java SE Support”与“将JDK 7升级到最新版本”两个选项里挑一个。JDK 7计划维护至2022年,迄今(面向付费用户)已发布了超过两百个更新补丁,最新版本为JDK 7 Update 221。对于JDK 7,还有一点值得提起的是,从JDK 7 Update 4起,Java SE的核心功能正式开始为MacOS X操作系统提供支持,并在JDK 7 Update 6中达到所有功能与Mac OS X完全兼容的程度;同时,JDK 7 Update 6还对ARM指令集架构提供了支持。至此,官方提供的JDK可以运行于Windows(不含Windows 9x)、Linux、Solaris和Mac OS X操作系统上,支持ARM、x86、x86-64和SPARC指令集架构,JDK 7也是可以支持Windows XP操作系统的最后一个版本[4]。
Sun Classic虚拟机的技术已经相当原始,这款虚拟机的使命也早已终结。但仅凭它“世界上第一款商用Java虚拟机”的头衔,就足够有令历史记住它的理由。。这款虚拟机只能使用纯解释器方式来执行Java代码,如果要使用即时编译器那就必须进行外挂,但是假如外挂了即时编译器的话,即时编译器就会完全接管虚拟机的执行系统,解释器便不能再工作了。
由于解释器和编译器不能配合工作,这就意味着如果要使用编译执行,编译器就不得不对每一个方法、每一行代码都进行编译,而无论它们执行的频率是否具有编译的价值。基于程序响应时间的压力,这些编译器根本不敢应用编译耗时稍高的优化技术,因此这个阶段的虚拟机虽然用了即时编译器输出本地代码,其执行效率也和传统的C/C++程序有很大差距,“Java语言很慢”的印象就是在这阶段开始在用户心中树立起来的。
一款名为Exact VM的虚拟机,它的编译执行系统已经具备现代高性能虚拟机雏形,如热点探测、两级即时编译器、编译器与解释器混合工作模式等。Exact VM因它使用准确式内存管理(Exact Memory Management,也可以叫Non-Conservative/Accurate Memory Management)而得名。准确式内存管理是指虚拟机可以知道内存中某个位置的数据具体是什么类型。譬如内存中有一个32bit的整数123456,虚拟机将有能力分辨出它到底是一个指向了123456的内存地址的引用类型还是一个数值为123456的整数,准确分辨出哪些内存是引用类型,这也是在垃圾收集时准确判断堆上的数据是否还可能被使用的前提。由于使用了准确式内存管理,Exact VM可以抛弃掉以前Classic VM基于句柄(Handle)的对象查找方式(原因是垃圾收集后对象将可能会被移动位置,如果地址为123456的对象移动到654321,在没有明确信息表明内存中哪些数据是引用类型的前提下,那虚拟机肯定是不敢把内存中所有为123456的值改成654321的,所以要使用句柄来保持引用值的稳定),这样每次定位对象都少了一次间接查找的开销,显著提升执行性能。
HotSpot既继承了Sun之前两款商用虚拟机的优点(如前面提到的准确式内存管理),也有许多自己新的技术优势,如它名称中的HotSpot指的就是它的热点代码探测技术(这里的描写带有“历史由胜利者书写”的味道,其实HotSpot与Exact虚拟机基本上是同时期的独立产品,HotSpot出现得还稍早一些,一开始HotSpot就是基于准确式内存管理的,而Exact VM之中也有与HotSpot几乎一样的热点探测技术,为了Exact VM和HotSpot VM哪个该成为Sun主要支持的虚拟机,在Sun公司内部还争吵过一场,HotSpot击败Exact并不能算技术上的胜利),HotSpot虚拟机的热点代码探测能力可以通过执行计数器找出最具有编译价值的代码,然后通知即时编译器以方法为单位进行编译。如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准即时编译和栈上替换编译(On-StackReplacement,OSR)行为[1]。通过编译器与解释器恰当地协同工作,可以在最优化的程序响应时间与最佳执行性能中取得平衡,而且无须等待本地代码输出才能执行程序,即时编译的时间压力也相对减小,这样有助于引入更复杂的代码优化技术,输出质量更高的本地代码。
得益于Sun/OracleJDK在Java应用中的统治地位,HotSpot理所当然地成为全世界使用最广泛的Java虚拟机,是虚拟机家族中毫无争议的“武林盟主”。
BEA System公司的JRockit与IBM公司的IBM J9。
JRockit虚拟机曾经号称是“世界上速度最快的Java虚拟机”(广告词,IBM J9虚拟机也这样宣传过,总体上三大虚拟机的性能是交替上升的),它是BEA在2002年从Appeal Virtual Machines公司收购获得的Java虚拟机。BEA将其发展为一款专门为服务器硬件和服务端应用场景高度优化的虚拟机,由于专注于服务端应用,它可以不太关注于程序启动速度,因此JRockit内部不包含解释器实现,全部代码都靠即时编译器编译后执行。除此之外,JRockit的垃圾收集器和Java Mission Control故障处理套件等部分的实现,在当时众多的Java虚拟机中也处于领先水平。JRockit随着BEA被Oracle收购,现已不再继续发展,永远停留在R28版本,这是JDK 6版JRockit的代号。
IBM J9直至今天仍旧非常活跃,IBM J9虚拟机的职责分离与模块化做得比HotSpot更优秀,由J9虚拟机中抽象封装出来的核心组件库(包括垃圾收集器、即时编译器、诊断监控子系统等)就单独构成了IBM OMR项目,可以在其他语言平台如Ruby、Python中快速组装成相应的功能。从2016年起,IBM逐步将OMR项目和J9虚拟机进行开源,完全开源后便将它们捐献给了Eclipse基金会管理,并重新命名为Eclipse OMR和OpenJ9[2]。如果为了学习虚拟机技术而去阅读源码,更加模块化的OpenJ9代码其实是比HotSpot更好的选择。如果为了使用Java虚拟机时多一种选择,那可以通过AdoptOpenJDK来获得采用OpenJ9搭配上OpenJDK其他类库组成的完整JDK。
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的话就可以把精力更多集中在业务方面。
HotSpot虚拟机中含有两个即时编译器,分别是编译耗时短但输出代码优化程度较低的客户端编译 器(简称为C1)以及编译耗时长但输出代码优化质量也更高的服务端编译器(简称为C2),通常它们 会在分层编译机制下与解释器互相配合来共同构成HotSpot虚拟机的执行子系统(这部分具体内容将在 本书第11章展开讲解)。
Graal编译器未来的前途可期,作为Java虚拟机执行代码的最新引擎,它的持续改进,会同时为 HotSpot与Graal VM注入更快更强的驱动力。
- 使用-XX:+UnlockExperimentalVMOptions-XX:+UseJVMCICompiler参数来启用Graal编译器。