从本文开始介绍JVM。
1. Java和JVM简介
前面学到的Java基本语法以及各种Java框架,其实底层都离不开JVM,Java程序是基于JVM运行的,使得无需考虑操作系统,相比于C/C++来说,正是JVM的存在,使得Java程序可以跨平台运行【本质上就是无论哪种操作系统,只要安装了JVM,Java程序都可运行。不过,不同操作系统的JVM,是不同的。换句话说,JVM的存在,使得程序员可以不同考虑操作系统之间的差异,一次编译,到处运行】。
本科阶段学习过汇编语言,很容易理解,编程语言随着计算机的发展,逐渐产生了适用于各种业务的专属语言,这些语言,毫无意外,都是方便上手学习,不像汇编、C/C++这些再提供对操作系统硬件方面的接口。这是为什么呢?因为各个时代,计算机有着不同的发展,早期,内存是瓶颈,所以程序对内存的占用有着严格限制。后来,内存技术的提升,使得内存不再那么重要,因此,完全可以基于C/C++编写出更加适用于人类学习上手使用的语言。比如Java、Python等等。从一定程度上说,语言就是人与计算机沟通的桥梁,语言越高级,人类就越容易理解,容易上手。但是随之而来的是,计算机就越不容易理解。而无论什么高级语言,最终都是会转换成机器指令,让计算机执行这些指令。语言越高级,翻译成机器指令也就越繁琐,效率也就越低,也就越不容易充分利用计算机硬件。
因此说,后期程序优化的时候,优化的就是效率,除了架构方面,还有就是硬盘、缓存、内存之间数据和业务需求之间的关系等等。JVM则是介于Java程序和操作系统之间的一层程序,JVM会将Java程序翻译成机器指令,使其可以运行。另外,JVM会进行垃圾回收等一些涉及到程序效率方面的工作。
参考文档:Java SE Specifications (oracle.com),里面有各个版本的语言规范和虚拟机规范。注意,这里的规范,指的就是标准,规则,比如计算机网络中的RFC1035等各种规范。不是具体的语言和虚拟机实现。因此说,对于某种规范,每个人都可以自己根据规范实现一种版本的语言或者某个版本的虚拟机。比如在Oracle官网下载的JDK中,Oracle实现的虚拟机叫做Hotspot。这是人家官方自己实现的。当然,除了这种之外,其他公司也有自己实现的虚拟机【按照官方同一种规范】。
1.1 Java历史进程
在2018年,调整JDK授权许可,即此时出现了两个版本的JDK:OpenJDK和OracleJDK。
- OpenJDK:仅6个月的维护期,超出维护期之后,如果出现bug,只能安装最新版本。(基于GNU的GPL协议,免费)
- OracleJDK:长期维护(基于OTN协议,商用付费,个人免费)
- 除此之外,二者几乎没什么太大的区别。
其中,JDK8和JDK11都是LTS版本,即long-term support,即长期维护的版本,出现了bug会及时修复。
2. 虚拟机和Java虚拟机
2.1 Java虚拟机
摘自JVM规范:
The Java Virtual Machine is the cornerstone of the Java platform. It is the component of the technology responsible for its hardware- and operating system-independence, the small size of its compiled code, and its ability to protect users from malicious programs.
The Java Virtual Machine is an abstract computing machine. Like a real computing machine, it has an instruction set and manipulates various memory areas at run time. It is reasonably common to implement a programming language using a virtual machine; the best-known virtual machine may be the P-Code machine of UCSD Pascal.
Java被称为跨平台语言,这是因为JVM的缘故。其实JVM除了支持Java语言的字节码文件外,还支持Scala、Kotlin等语言的字节码文件,因此说,JVM也可被称为跨语言平台。(注意,Java虚拟机只是支持上述语言的字节码文件,并不是编译语言源代码,对Java同理。JVM只关心字节码文件,只要给的字节码文件是按照JVM要求的规范格式生成的,那么就支持运行。)
For the sake of security, the Java Virtual Machine imposes strong syntactic and structural constraints on the code in a class file. However, any language with functionality that can be expressed in terms of a valid class file can be hosted by the Java Virtual Machine. Attracted by a generally available, machine-independent platform, implementors of other languages can turn to the Java Virtual Machine as a delivery vehicle for their languages.
我们平时说的Java字节码文件,指的是用Java语言编译成的字节码。准确的说,其实任何能在JVM平台上执行的字节码格式都是一样的,所以统称为:JVM字节码。
不同的编译器,编译出相同的字节码文件,字节码文件也可以在不同的JVM上运行。
JVM与Java语言并没有必然联系,JVM只与特定的二进制文件格式——Class文件格式所关联。Class文件包含了JVM指令集(或者成为字节码、Bytecodes)和符号表,还有一些其他辅助信息。只要某种语言源程序可以编译成这种Class文件格式的文件,那么该文件就能在JVM上运行,即JVM就支持该语言。
只不过,最开始Java语言团队,构建的JVM主要用于Java程序编译后的字节码文件,所以JVM看起来是和Java密切相关的。
总体来说,Java虚拟机是一台执行字节码的虚拟计算机,它拥有独立的运行机制。换句话说,JVM是运行字节码文件的运行环境,负责装载字节码到其内部,解释/编译为对应平台上(Linux、Windows、Mac)的机器指令执行。JVM主要有以下特点:
- 一次编译,到处运行
- 自动内存管理
- 自动垃圾回收功能
降低了内存泄露和内存溢出的风险。
2.2 虚拟机
虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。
- Virtual Box、VMWare就属于系统虚拟机,他们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。
- 程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令,我们成为Java字节码指令。
无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。因此,个人认为,JVM调优似乎就是如何合理地运用JVM所提供的资源,高效利用资源。JVM
Java程序先通过编译器编译成字节码文件(典型的编译器就是javac,前端编译器),然后通过JVM中的后端编译器解释运行。之前学习的内容是通过Java API来编写Java程序,现在就是学习底层JVM是如何解释运行的。
3. JVM整体结构
前面提到过,针对JVM规范,现在有很多种JVM,本文主要针对Hotspot虚拟机。Hotspot虚拟机是目前市面上高性能虚拟机的代表作之一。它采用解释器与即时编译器并存的架构。在今天,Java程序的运行性能早已脱胎换股,已经达到了和C/C++程序一较高下的地步。
整体内存结构图如下所示:
上层(类加载子系统/类装载器子系统)
将class字节码文件生成一个大的Class对象。加载、链接、初始化。
中层(运行时数据区)
方法区、堆、栈(Java栈/虚拟机栈、本地方法栈)、程序计数器(PC寄存器)。多线程共享方法区和堆。剩余三个,每个线程单独保留一份。
下层(执行引擎)
解释运行(解释器、JIT编译器(后端)、垃圾回收器等等)。执行引擎就是将字节码文件翻译成机器指令。
总体上说,最重要的是类加载子系统和执行引擎。通过类加载子系统获取到class文件(程序)的类的信息(常量、变量、方法等等),将这些内容有组织地分配在内存中(运行时数据区)。执行引擎则是逐条解释指令,执行程序。
4. Java代码执行流程
有前面可以知道,通过编译器【编译原理】,将Java源码编译成字节码文件,然后通过JVM翻译成机器指令,调用操作系统执行命令。
5. JVM的架构模型(了解,涉及到本科的计组和汇编)
Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构。Hotspot虚拟机是基于栈的指令集架构。
具体来说,这两种架构之间的区别:
- 基于栈式架构的特点
- 设计和实现更简单,适用于资源受限的系统;
- 避开了寄存器的分配难题:使用零地址指令方式分配。
- 指令流中的指令大部分是零地址指令,其执行过程依赖于栈。指令集更小【但是完成同样的操作,用到的指令数更多】,编译器容易实现。
- 不需要硬件支持,可移植性更好,更好实现跨平台
- 基于寄存器架构的特点
- 典型的应用是X86的二进制指令集:比如传统的PC以及Android的Davlik虚拟机。
- 指令集架构完全依赖硬件,可移植性差
- 性能优秀和执行更高效
- 花费更少的指令去完成一项操作
- 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式结构的指令集却是以零地址指令为主。
总体上来说,由于跨平台性的设计,Java的指令都是依据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
6. JVM的生命周期
6.1 启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建的一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现(即哪个虚拟机,因为规范只是规定了启动流程,具体的实现由实现人自己决定,如Hotspot)指定的。
即运行程序,需要加载这个类,而这个类是有父类的(最终父类为Object),不同的父类其所用的类加载器是不同的,比如自定义类、Object类等等,这些类采用不同的类加载器。其实就是某个类需要依赖另一个类,而最根本的那个类就是需要JVM创建一个初始类,这时候通过引导类加载器加载。【似乎可以认为,一个程序就会对应一个JVM】
6.2 执行
一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止了。执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程。
6.3 退出
虚拟机退出有如下几种情况:
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 由于操作系统出现错误而导致Java虚拟机进程终止
- 某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作。
- 除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载Java虚拟机时,Java虚拟机的退出情况。
7. JVM的发展历程
7.1 Sun Classic VM
早在1996年Java1.0版本的时候,Sun公司发布了一款名为Sun Classic VM的Java虚拟机,它同时也是世界上第一款商用的Java虚拟机,JDK1.4时完全被淘汰。
这款虚拟机内部只提供解释器。如果使用JIT编译器,就需要进行外挂。但是一旦使用了JIT编译器,JIT就会接管虚拟机的执行系统。解释器就不再工作。解释器和编译器不能配合工作。
现在Hotspot内置了此虚拟机。解释器的效率比较低,如果单纯采用解释器,程序总体上效率较低。而JIT即时编译器,如果发现某段代码反复执行,那么就会将其翻译成机器指令,缓存下来。方便后续运行此段代码时,直接运行机器指令即可。从而提升效率。
但是JIT翻译时,需要暂停时间,导致时间开销比较大,所以既不能翻译代码,缓存起来。也不能不翻译,所以只能翻译一部分重复执行的代码。最开始,Java程序是比C/C++程序慢的,但是经过这么多年发展,经过JIT的加入,Java程序运行效率已经很高了。
7.2 Exact VM
为了解决上面虚拟机的问题,JDK1.2时,sun提供了此虚拟机。
Exact Memory Management(Non-Conservative/Accurate Memory Management),虚拟机可以知道内存中某个位置的数据具体是什么类型。
具备现代高性能虚拟机的雏形:
- 热点代码探测
- 编译器与解释器混合工作模式
该虚拟机只在Solaris平台短暂使用,其他平台上还是classic VM,最终被Hotspot虚拟机替换。
7.3 HotSpot VM(重点,三大商用VM之一)
HotSpot历史:
- 最初由一家名为“Longview Technologies”的小公司设计
- 1997年,此公司被Sun收购;2009年,Sun公司被甲骨文收购
- JDK1.3时,HotSpot VM称为默认虚拟机
目前HotSpot占有绝对的市场地位,称霸武林。
- 不管是现在仍在广泛使用的JDK6,还是使用比例较多的JDK8,默认的虚拟机都是HotSpot
- Sun/Oracle JDK和OpenJDK的默认虚拟机
- 因此,后续文章中介绍的概念机制等,基本上默认都是HotSpot。(比如其他两个商用虚拟机都没有方法区的概念)
从服务器、桌面到移动端、嵌入式都有应用。
名称中的HotSpot指的就是他的热点代码探测技术:
- 通过计数器找到最具编译价值代码,触发即时编译或栈上替换。
- 通过即时编译器与解释器协同工作,在最优化的程序响应时间(解释器)与最佳执行性能(即时编译器)取得平衡。
7.4 JRockit(三大商用VM之一)
该虚拟机专注于服务器端应用。即可以不太关注程序启动速度,因此JRockit内部不包含解释器实现,全部代码都靠即时编译器编译后执行。
大量的行业基准测试显示,JRockit JVM是世界上最快的JVM。
目前Oracle已经将HotSpot和JRockit进行了简单整合。
7.5 J9(三大商用VM之一)
和HotSpot类似,在服务端、桌面应用以及嵌入式等多领域都有应用。
7.6 KVM和CDC/CLDC HotSpot
在Java ME(移动端),曾经开发了两款虚拟机CDC/CLDC HotSpot、Implementation VM。现在随着移动端被Android、IOS占有,Java ME基本上退出了市场。KVM(Kilobyte)是CLDC-HI早期产品,现在在低端设备上有一定的市场。
7.7 其他
上面几个都是通用的虚拟机。Azul VM、BEA Liquid VM、Zing JVM等几个都是与硬件绑定的虚拟机,即对硬件做了自适应。在特殊硬件上,效率较高。
另外,Apache harmony、Microsoft JVM等,都是曾经比较优秀的JVM。此外,国内的TaobaoJVM(AliJVM),也是比较优秀的JVM。
7.8 非JVM的VM
Dalvik VM应用于Android系统,只能成为虚拟机,不能被称为Java虚拟机,因为没有遵守JVM规范,因此不能直接执行class文件。
8. 备注
参考B站《尚硅谷》。