文章目录
- 1、Java及JVM简介
- 1.1、Java是跨平台的语言
- 1.2、JVM是跨语言的平台
- 2、Java发展里程碑
- 3、Open JDK和Oracle JDK
- 4、虚拟机与JVM
- 4.1、虚拟机
- 4.2、JVM
- 5、JVM整体结构
- 6、Java代码执行流程
- 7、JVM的架构模型
- 7.1、基于栈式架构的特点
- 7.2、基于寄存器架构的特点
- 8、JVM的生命周期
- 9、JVM的明星产品
- 9.1、HotSpot VM
- 9.2、Taobao JVM
- 9.3、Graal VM
- 9.4、其他JVM
初步介绍Java和JVM的关系,阐述深入了解JVM的原因,以及JVM的内部架构和发展历程,为更加全面地了解并使用Java语言打下坚实的基础,更好地解决实际项目中的性能优化和部分项目瓶颈问题。
1、Java及JVM简介
1.1、Java是跨平台的语言
世界上没有最好的编程语言,只有最适用于具体应用场景的编程语言。Java语言的跨平台性是“一次编译,到处运行”,编写的以“.java”结尾的源文件,经过编译器编译之后生成字节码文件,字节码文件可以在不同的平台上进行解释运行。针对不同操作系统安装对应平台的JVM虚拟机即可运行Java程序,如下图所示:
按照技术所服务的领域来划分,Java技术体系可以分为以下四条主要的产品线:
- Java SE(Standard Edition):支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核心API,这条产品线在JDK 6以前被称为J2SE。
- Java EE(Enterprise Edition):支持使用多层架构的企业应用(如ERP、MIS、CRM应用)的Java平台,除了提供Java SE API外,还对其做了大量有针对性的扩充,并提供了相关的部署支持,这条产品线在JDK 6以前被称为J2EE,在JDK 10以后被Oracle放弃,捐献给Eclipse基金会管理,此后被称为Jakarta EE。
- Java ME(Micro Edition):支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,并加入了移动终端的针对性支持,这条产品线在JDK 6以前被称为J2ME。有一点读者请勿混淆,现在在智能手机上非常流行的、主要使用Java语言开发程序的Android并不属于Java ME。
- Java Card:支持Java小程序(Applets)运行在小内存设备(如智能卡)上的平台。
1.2、JVM是跨语言的平台
,如果说Java是跨平台的语言,那JVM就是跨语言的平台。首先,我们看一下JVM的官方文档,如下图所示:
JVM是整个Java平台的基石,是Java技术用于实现硬件无关与操作系统无关的关键部分,是Java语言生成出极小体积的编译代码的运行平台,是保障用户机器免于恶意代码损害的屏障。
JVM可以看作是一台抽象的计算机。如同个人计算机,它有自己的指令集以及各种运行时内存区域。使用虚拟机来实现一门程序设计语言是相当常见的,业界中流传最为久远的虚拟机可能是UCSD Pascal的P-Code虚拟机。
第一个JVM的原型机是由Sun Microsystems公司实现的,它用在一种类似PDA(Person Digital Assistant,掌上电脑)的手持设备上,以仿真实现JVM指令集。时至今日,Oracle已经将许多JVM实现应用于移动设备、台式机和服务器等领域。但JVM并不局限于特定的实现技术、主机硬件和主机操作系统。JVM也不局限于特定的代码执行方式,它虽然不强求使用解释器来执行程序,但是也可以通过把自己的指令集编译为实际CPU的指令来实现。它可以通过微代码(Microcode)来实现,甚至可以直接在CPU中实现。
JVM与Java语言并没有必然的联系,它只与特定的二进制文件格式——class文件格式所关联。class文件包含JVM指令集[或者称为字节码(Bytecode)]和符号表,以及其他一些辅助信息。
基于安全方面的考虑,JVM在class文件中施加了许多强制性的语法和结构化约束,凡是能用class文件正确表达出来的编程语言,都可以放在JVM里面执行。由于它是一个通用的、与机器无关的执行平台,所以其他语言的实现者都可以考虑将JVM作为那些语言的交付媒介。
随着Java 7的正式发布,JVM的设计者通过JSR-292规范基本实现了在JVM平台上运行非Java语言编写的程序,如下图所示:
不同的编译器,可以编译出相同的字节码文件,字节码文件也可以在不同的JVM上运行。
JVM根本不关心运行在其内部的程序到底是使用何种编程语言编写的,它只关心“字节码”文件。也就是说JVM拥有语言无关性,并不会单纯地与Java语言“终身绑定”,只要其他编程语言的编译结果满足并包含JVM的内部指令集、符号表以及其他的辅助信息,它就是一个有效的字节码文件,就能够被虚拟机所识别并装载运行。现在开发语言越来越多,虽然Java语言并不是最强大的语言,但JVM可以说是业内公认的最强大的虚拟机。
我们平时说的Java字节码,指的是用Java语言编译成的字节码。准确地说,任何能在JVM平台上执行的字节码格式都是一样的。所以应该统称为“JVM字节码”。
Java平台上的多语言混合编程正在成为主流,通过特定领域的语言去解决特定领域的问题是当前软件开发应对日趋复杂的项目需求的一个方向。
试想一下,在一个项目之中,并行处理使用Clojure语言,展示层使用JRuby/Rails语言,中间层则使用Java语言,每个应用层都使用不同的编程语言来完成。而且,接口对每一层的开发者都是透明的,各种语言之间的交互不存在任何困难,就像使用自己语言的原生API一样方便,因为它们最终都运行在一个虚拟机上。
对这些运行于JVM之上、Java之外的语言,来自系统级的、底层的支持正在迅速增强,以JSR-292为核心的一系列项目和功能改进(如Da Vinci Machine项目、Nashorn引擎、InvokeDynamic指令、java.lang.invoke包等),推动JVM从“Java语言的虚拟机”向“多语言虚拟机”的方向发展。
JVM在整个JDK体系中处于什么位置呢?如下图所示,JDK包含了JRE,JRE包含了JVM。
2、Java发展里程碑
2020年,JDK 15发布,ZGC转正,支持的平台包括Linux、Windows和macOS。同时,Shenandoah垃圾回收算法终于从实验特性转变为产品特性。
3、Open JDK和Oracle JDK
在调整JDK授权许可之后,每次发布JDK的新版本的时候都会同时发布两个新的Open JDK版本和Oracle JDK版本。两个版本的主要区别是基于的协议不同,Open JDK基于GPL协议,Oracle JDK基于OTN的协议。Open JDK的维护期间为半年,即半年更新一个版本,一旦出现问题就需要更新JDK的版本。Oracle JDK的维护期为3年,但是商业使用需要付费。两者之间还有很多代码实现是一样的,例如JDBC、javac、core libraries等,如下图所示:
在JDK 11之前,Oracle JDK中还会存在一些Open JDK中没有的、闭源的功能。但在JDK 11中,我们可以认为Open JDK和Oracle JDK代码实质上已经完全一致。
4、虚拟机与JVM
4.1、虚拟机
所谓虚拟机,就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。
大名鼎鼎的Visual Box、VMware就属于系统虚拟机,它们完全是对物理计算机进行仿真,提供了一个可运行完整操作系统的软件平台。
程序虚拟机的典型代表就是JVM,它专门为执行单个计算机程序而设计,在JVM中执行的指令称为Java字节码指令。
无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
4.2、JVM
JVM是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成。各种语言可以共享JVM带来的跨平台性,此外JVM还包含可以做到自动垃圾回收的优秀垃圾回收器以及可靠的即时编译器,这些都是JVM平台的优点。
JVM就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数、怎么处理操作数、处理结果放在哪里,等等。
JVM是运行在操作系统之上的,它与硬件没有直接的交互,如下图所示:
5、JVM整体结构
HotSpot VM是目前市面上高性能虚拟机的代表作之一,它采用解释器与即时编译器并存的架构。在今天,Java程序的运行性能早已脱胎换骨,已经达到了可以和C/C++程序一较高下的地步。首先看一下JVM的整体结构图,如下图所示:
该架构可以分成三层:
- 最上层:类装载器子系统。javac编译器将编译好的字节码文件,通过Java类装载器执行机制,把对象或字节码文件存放在JVM内存划分区域。
- 中间层:运行时数据区(Runtime Data Area)。主要是在Java代码运行时用于存放数据的区域,包括方法区、堆、Java栈、程序计数器、本地方法栈。
- 最下层:执行引擎层。执行引擎包含解释器、JIT(Just In Time)编译器和垃圾回收器(Garbage Collection,GC)。
6、Java代码执行流程
Java源文件经过编译器的词法分析、语法分析、语义分析、字节码生成器等一系列过程生成以“.class”为后缀的字节码文件。Java编译器编译过程中,任何一个节点执行失败都会造成编译失败。字节码文件再经过JVM的类加载器、字节码校验器、翻译字节码(解释执行)或JIT编译器(编译执行)的过程编译成机器指令,提供给操作系统进行执行。
JVM的主要任务就是将字节码装载到其内部,解释/编译为对应平台上的机器指令执行。JVM使用类加载器(Class Loader)装载class文件,虽然各个平台的JVM内部实现细节不尽相同,但是它们共同执行的字节码内容却是一样的。类加载完成之后,会进行字节码校验,字节码校验通过,JVM解释器会把字节码翻译成机器码交由操作系统执行。
早期,我们说Java是一门解释型语言,因为在Java刚诞生,即JDK1.0的时候,Java的定位是一门解释型语言,也就是将Java程序编写好之后,先通过javac将源码编译为字节码,再对生成的字节码进行逐行解释执行。现在我们提到Java,更多地认为其是一门半编译半解释型的语言,因为Java为了解决性能问题,采用了一种叫作JIT即时编译的技术,也就是将执行比较频繁的整个方法或代码块直接编译成本地机器码,以后执行这些方法或代码时,直接执行生成的机器码即可。换句话说,在HotSpot VM内部,即时编译器与解释器是并存的,通过编译器与解释器的协同工作,既可以保证程序的响应时间,同时还能够提高程序的执行性能。目前市面上大多数主流虚拟机都采用此架构。Java代码的具体执行流程,如下图所示:
7、JVM的架构模型
Java编译器输入的指令流是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构。具体来说,这两种架构之间的区别如下:
7.1、基于栈式架构的特点
- (1)设计和实现更简单,适用于资源受限的系统。比如机顶盒、打印机等嵌入式设备。
- (2)避开了寄存器的分配难题,使用零地址指令方式分配,只针对栈顶元素操作。
- (3)指令流中的指令大部分是零地址指令,其执行过程依赖操作栈。指令集更小,编译器更容易实现。
- (4)不需要硬件支持,可移植性更好,可以更好地实现跨平台。
7.2、基于寄存器架构的特点
- (1)典型的应用是x86的二进制指令集。比如传统的PC以及Android的Davlik虚拟机。
- (2)指令集架构则完全依赖硬件,可移植性差。
- (3)指令直接由CPU来执行,性能优秀和执行更高效。
- (4)花费更少的指令去完成一项操作。
- (5)在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。
案例1:执行2+3这种逻辑操作,其代码示例如下:
基于栈的计算流程(以JVM为例):
整个执行流程如下:
- (1)常量2压入操作数栈。
- (2)弹出操作数栈栈顶元素,保存到局部变量表第1个位置(把常量2保存到局部变量表)。
- (3)常量3压入操作数栈。
- (4)弹出操作数栈栈顶元素,保存到局部变量表第2个位置(把常量3保存到局部变量表)。
- (5)变量表中第1个变量压入操作数栈。
- (6)变量表中第2个变量压入操作数栈。
- (7)操作数栈中的前两个int相加,并将结果压入操作数栈栈顶。
- (8)弹出操作数栈栈顶元素,保存到局部变量表第0个位置(把结果5保存到局部变量表)。
基于寄存器的计算流程如下:
mov eax,2 //将eax寄存器的值设为2add eax,3 //将eax寄存器的值加3
接下来详细讲解一下基于栈的计算流程,案例2是在案例1的类中新增calc()方法。案例2代码如下:
反编译后的结果如下:
字节码文件的反编译命令是javap,例如使用下面的命令反编译StackStruTest.class文件:javap -v StackStruTest.class。
指令执行流程如下:
- (1)常量100压入操作数栈。
- (2)弹出操作数栈栈顶元素,保存到局部变量表第1个位置(把常量100保存到局部变量表)。
- (3)常量200压入操作数栈。
- (4)弹出操作数栈栈顶元素,保存到局部变量表第2个位置(把常量200保存到局部变量表)。
- (5)常量300压入操作数栈。
- (6)弹出操作数栈栈顶元素,保存到局部变量表第3个位置(把常量300保存到局部变量表)。
- (7)变量表中第1个变量压入操作数栈。
- (8)变量表中第2个变量压入操作数栈。
- (9)操作数栈中的前两个int相加,并将结果压入操作数栈栈顶。
- (10)变量表中第3个变量压入操作数栈。
- (11)操作数栈中的前两个int相乘,并将结果压入操作数栈栈顶。
- (12)弹出操作数栈栈顶元素,保存到局部变量表第0个位置(把结果90000保存到局部变量表)。
我们来总结一下,由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。基于栈式架构的优点是跨平台、指令集小、编译器容易实现,缺点是性能较差,实现同样的功能需要更多的指令。
时至今日,尽管嵌入式平台已经不是Java程序的主流运行平台(准确来说,应该是HotSpotVM的宿主环境已经不局限于嵌入式平台),那么为什么不将架构更换为基于寄存器的架构呢?
首先基于栈的架构从设计和实现上更简单一些,如入栈出栈等方面;其次在非资源受限的平台当中也是可用的,即体现了它的跨平台性。
8、JVM的生命周期
JVM的生命周期包含三个状态:JVM的启动、JVM的执行和JVM的退出。
JVM可以通过Java命令启动,接着通过引导类加载器(Bootstrap Class Loader)加载类文件,最后找到程序中的main()方法,去执行Java应用程序。
JVM的执行表示一个已经启动的JVM开始执行Java程序。JVM通过main()方法开始执行程序,程序结束时JVM就停止。执行一个Java程序的时候,真正在执行的是一个叫作JVM的进程,通常情况下,一个Java程序对应一个JVM进程。
JVM的退出有如下几种情况:
- (1)Java应用程序正常执行结束,即当所有的非守护线程执行结束。
- (2)Java应用程序在执行过程中遇到了异常或错误而异常终止,比如发生内存溢出导致程序结束。
- (3)由于操作系统出现错误而导致JVM进程终止,比如机器宕机。
- (4)用户手动强制关闭JVM,比如使用kill命令。
- (5)某线程调用Runtime类或System类的exit()方法。
9、JVM的明星产品
9.1、HotSpot VM
相信很多Java程序员都听说过HotSpot虚拟机,它是Sun/Oracle JDK和Open JDK中的默认虚拟机,也是目前使用范围最广的JVM。然而HotSpot虚拟机最初由一家名为Longview Technologies的小公司设计,这款虚拟机在即时编译等方面有着优秀的理念和实际成果,Sun公司在1997年收购了Longview Technologies公司,从而获得了HotSpot虚拟机。2009年,Sun公司被Oracle公司收购,在JDK 1.3中HotSpot VM成为默认虚拟机。
HotSpot VM继承了Sun之前两款商用虚拟机的优点,也有许多自己新的技术优势,比如它的热点代码探测技术。程序执行过程中,一个被多次调用的方法,或者一个方法体内部循环次数较多的循环体都可以被称为“热点代码”,探测到热点代码后,通知即时编译器以方法为单位触发标准即时编译和栈上替换编译(On-Stack Replacement,OSR)。此外,HotSpot VM是编译器与解释器同时存在的,当程序启动后,解释器可以马上发挥作用,省去编译的时间,立即执行。编译器把代码编译成机器码,需要一定的执行时间,但编译为机器码后,执行效率高。通过编译器与解释器恰当地协同工作,可以在最优的程序响应时间与最佳的执行性能中取得平衡,而且无须等待本地代码输出再执行程序,即时编译的时间压力也相对减小。不管是仍在广泛使用的JDK 6,还是使用比例较高的JDK 8中,默认的虚拟机都是HotSpot。
对于HotSpot虚拟机从服务器、桌面到移动端、嵌入式都有应用。得益于Sun/Oracle JDK在Java应用中的统治地位,HotSpot理所当然地成为全世界使用最广泛的JVM,是虚拟机家族中毫无争议的“武林盟主”。
9.2、Taobao JVM
Taobao JVM由Ali JVM团队发布。阿里巴巴是国内使用Java最强大的公司,覆盖云计算、金融、物流、电商等众多领域,需要解决高并发、高可用、分布式的复合问题,有大量的开源产品。Ali JVM团队基于Open JDK开发了自己的定制版本Alibaba JDK,简称AJDK。它是整个阿里Java体系的基石,也是基于OpenJDK HotSpot VM发布的国内第一个优化、深度定制且开源的高性能服务器版JVM。
其中创新的GCIH(GC Invisible Heap)技术实现了off-heap,即将生命周期较长的Java对象从heap之中移到heap之外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升GC的回收效率的目的。同时GCIH中的对象还能够在多个JVM进程中实现共享。Taobao JVM中使用crc32指令实现JVM intrinsic降低JNI的调用开销,提供了PMU hardware的Java profiling tool和诊断协助功能,以及专门针对大数据场景的ZenGC。
Taobao JVM应用在阿里巴巴产品上性能高,硬件严重依赖Intel的CPU,损失了兼容性,但提高了性能。目前已经在淘宝、天猫上线,把Oracle官方JVM版本全部替换了。
9.3、Graal VM
2018年4月,Oracle Labs新公开了一项黑科技:Graal VM,如下图所示:
从它的口号“Run Programs Faster Anywhere”就能感觉到一颗蓬勃的野心,这句话显然是与1995年Java刚诞生时的“Write Once,Run Anywhere”遥相呼应。
Graal VM被官方称为“Universal VM”和“Polyglot VM”,这是一个在HotSpot虚拟机基础上增强而成的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用,这里“任何语言”包括了Java、Scala、Groovy、Kotlin等基于JVM之上的语言,还包括了C、C++、Rust等基于LLVM的语言,同时支持其他如JavaScript、Ruby、Python和R语言等。Graal VM可以无额外开销地混合使用这些编程语言,支持不同语言中混用对方的接口和对象,也能够支持这些语言使用已经编写好的本地库文件。
9.4、其他JVM
其他的JVM有Java Card VM、Squawk VM、JavaInJava、Maxine VM、Jikes RVM、IKVM.NET、Jam VM、Cacao VM、Sable VM、Kaffe、Jelatine JVM、Nano VM、MRP、Moxie JVM等。
具体JVM的内存结构,其实取决于其实现,不同厂商的虚拟机,或者同一厂商发布的不同版本,都有可能存在一定差异。我们应该主要以Oracle HotSpot虚拟机来展开学习。