感谢尚硅谷
Java和JVM
我们在平时使用讲解的时候,使用JDK8来进行讲解,但是考虑到有一些变化和新特性,会切换到不同的版本,到时候会有说明。
Java程序:一次编写,到处运行,靠的就是Java虚拟机
但是现在貌似叫Java虚拟机不太合适了,因为下面的这些语言在通过各自的编译器转换为遵循Java虚拟机规范字节码文件之后,都可以在Java虚拟机上运行。也就是说,只要最后的字节码文件遵循Java虚拟机的规范,都可以在Java虚拟机上运行
JVM字节码
我们平常所说的字节码,指的是使用Java语言编写成为的字节码。
准确地来说任何能够在JVM运行的字节码都是一样的,所以应该统一称为:JVM字节码
不同的编译器可以编译出相同的字节码文件,字节码文件也可以在b不同的JVM上运行
Java虚拟机与Java语言并没有必然的联系,它只与特定的二进制文件格式——Class文件有着关系,但是恰好,Java的字节码文件就是这种特定的文件格式。
Class文件中包含了Java虚拟机指令集(或者称为字节码,Bytecodes)和符号表,还有其他的一些辅助信息。
多语言混合编程
通过特定领域的语言去解决特定领域的问题是软件开发中渐渐靠拢的一个方向。
每一个特定领域的语言负责自己的领域,并且接口对于每一层的开发者都是透明的,各种语言可以最终使用一套字节码在一个虚拟机上运行,这样就保证了各个语言之间的交互不存在任何问题,调用其他语言的API就像调用自己语言的API一样方便。
对于这些可以运行在Java虚拟机上、Java之外的语言,底层的实现正在增强。
以JSR-292为核心的一系列项目和功能改进,推动Java虚拟机从Java语言的虚拟机向多语言虚拟机迈进。
如何真正搞懂JVM
最好的办法是自己动手写一个Java虚拟机
JVM发展中的重大事件
- 2000年,JDK1.3发布,同时发布了Java HotSpot Virtual Machine,成为了Java的默认虚拟机
- 2003年底,Java平台的Scala正式发布,同年Groovy也加入了Java
- 2006年,Java开源并建立了OpenJDK。顺理成章,Hotspot虚拟机也成为了OpenJDK中的默认虚拟机
- 2007年,Java平台迎来了Clojure
- 2008年,Oracle收购BEA,得到了JRockit虚拟机
- 2010年,Oracle收购了Sun,获得了Java商标和Hotspot虚拟机
- 2011年,JDK7发布,正式启用了新的垃圾回收器G1
- 2017年,JDK9发布。将G1设置为默认GC,代替了CMS。IBM的J9开源,形成了Open J9的社区
- 2018年,发布了革命性的ZGC,调整JDK授权许可
- 2019年,JDK12发布,加入RedHat领导开发的Shenandoah GC
虚拟机和Java虚拟机
- 虚拟机:Virtual Machine
就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。
- Java虚拟机
- Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成
- JVM平台的各种语言可以共享Java虚拟机带来的跨平台性,优秀的垃圾回收器,以及可靠的即时编译器
- Java技术的核心就是Java虚拟机,因为所有的Java程序都运行在Java虚拟机内部
特点:
- 一次编译,到处执行
- 自动内存管理
- 自动垃圾回收
缺点:出现灾难性问题时一脸懵逼
大体上,虚拟机可以分为系统虚拟机和程序虚拟机
- 系统虚拟机:完全对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。VMware就是典型的系统虚拟机。
- 程序虚拟机:专门为执行单个计算机程序而设计。Java虚拟机就是典型的程序虚拟机。
无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
JVM的位置
JVM的整体结构
详细版本
Java代码的执行流程
简单来讲:Java程序–>编译为字节码文件–>在各自系统的虚拟机下运行
但是事实上,Java代码的执行流程细节一点是这样的:
我们可以看到,Java的编译器是有一部分流程的,只要流程中有一部分执行失败就不会产生最后的字节码文件
并且可以可以看到,在Java虚拟机下面,我们的字节码文件还要再次通过JIT编译器进行编译,这个主要是将字节码指令编译为机器指令
并且对于一些热点的指令还要进行缓存到方法区中,以后直接调用即可。
JIT编译器主要是为了程序执行的性能。所以现在的主流编译器都采用解析+编译二者并存的情况。
JVM的架构模型
指令集的架构模型分为两种:
- 基于栈的指令集架构
- 基于寄存器的指令集架构
这两种指令集架构有一些区别
基于栈的指令集架构
- 设计和实现更加简单,适用于资源受限的系统
- 避开了寄存器的分配难题:使用零地址指令方式分配
- 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器更加容易实现
- 不需要硬件支持,可移植性更好,更好实现跨平台
基于寄存器的指令集架构
- 典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虚拟机指令集架构则完全依赖于硬件,可移植性差
- 性能更加优秀,执行更加高效
- 花费更少的指令去完成一项操作
- 大部分情况下,基于寄存器架构的指令集往往都是一地址指令,二地址指令和三地址指令为主,而基于栈架构指令集以零地址指令集为主
Java编译器输入的指令流基本上是一种基于栈的指令集架构
说明一下,零地址,一地址,二地址,三地址是什么意思
我们平常在进行一项操作的时候,通常是 地址:操作数 这种形式一地址指令就是一个地址,二地址指令就是两个地址,…。零地址指令就是没有地址,只有操作数。
JVM的生命周期
虚拟机的启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建的一个初始类来完成的,这个类是由虚拟机的具体实现指定的。
举个例子,我们现在想要吃苹果,但是发现没有苹果树,所以我们首先要种一棵苹果树。
在Java中,我们想要加载一个类,但是发现没有一些必要的结构,比如父类和一些必要的结构,所以我们首先要将Java虚拟机来启动起来,提供这个必要的环境,然后才能够加载类
虚拟机的执行
一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序
程序开始执行时它才运行,程序结束时它就停止
执行一个所谓的Java程序时,真真正正在执行的是一个叫做Java虚拟机的进程
虚拟机的退出
有如下几种情况
- 程序正常执行结束
- 在执行过程中遇到了异常或者错误而异常终止
- 由于操作系统出现了错误而导致Java虚拟机进程终止
- 某线程调用Runtime类或者System类的exit方法,或者Runtime类的halt方法,并且Java安全管理器也允许这次操作
- JNI(Java Native Interface)规范描述了使用JNI Invocation API来加载或者卸载 Java虚拟机时,Java虚拟机的推出情况
对于exit或者halt方法,最终调用的都是一个本地方法halt0(),只不过是换了换名字而已
我们之前讲过,JVM的整体结构中,有一个叫做运行时数据区的东西
这个运行时数据区可以理解为运行时环境,一个JVM其实就对应着一个运行时环境,那么我们的运行时环境其实对应的类就是Runtime
这个Runtime是一个饿汉式单例
JVM的发展历程
Sun Classic VM
- 1996年的Java1.0版本,Sun公司发布了Sun Classic VM这款虚拟机,也是世界上第一款商用Java虚拟机,在JDK1.4时被淘汰
- 这款虚拟机只提供解释器
- 如果使用JIT编译器,就需要进行外挂,但是一旦使用JIT编译器,那么JIT就会接管虚拟机的执行系统,解释器就不会进行工作。这样一来,解释器和编译器不能配合工作
- 现在hotsopt内置了此虚拟机
我们之前说执行引擎的时候,说现在主流的执行引擎都带有解释器和即时编译器,但是classic这个虚拟机只有解释器,没有即时编译器,而且即时编译器外挂之后就会代替解释器的工作,所以以现在的眼光来看,这是一款不太成功的产品
那么为什么说这个是一个不太成功的产品呢?因为考虑到它的性能原因。
事实上,解释器和JIT编译器二者都可以对字节码进行解析,并不是说哪一个必须要依赖于另一个。所以在 sun classic vm中,只提供了解释器
但是解释器有一个缺点,就是每一行的代码都是重新解释的,哪怕你写一个执行两千次的for循环,解释器也会逐行进行解释。
但是JIT编译器不会,JIT编译器有一个很巨大的作用是将代码即时地编译成为本地机器指令,然后将这些指令缓存起来,这样下次就不用再次进行解释执行了,提高了工作效率
那么我们说,classic要么使用解释器,要么使用JIT编译器,两者不能够协同工作。
对于解释器来说,它对一些热点代码(我们称一些反复执行的代码为热点代码)也只能够每次都要解释运行
对于JIT编译器来说,将所有的代码全都转成本地机器指令缓存起来明显是不现实的,因为转换为本地机器指令也是需要一段时间的,那么这就回造成开启时响应时间过长的结果
所以正确的做法是,对于一般情况的代码我们使用解释器来解释执行,对于一些热点代码我们使用JIT转换为本地机器指令缓存起来。
Exact VM
- JDK1.2时,Sun公司提供了这个虚拟机
- Exact Memory Management:准确式内存管理
- 也可以叫做Non-Conservative/Accurate Memory Manegement
- 准确式内存管理的意思是:可以知道内存中具体的某个位置的数据是什么类型
- Exact VM 具备现代高性能虚拟机的雏形
- 热点探测
- 编译器与解释器混合工作模式
- 只在Solaris平台短暂使用,其他平台还是Classic VM,最终被Hotspot代替
准确式内存管理是classic中没有的东西,它是这么个意思:
现在内存空间中有一个地址放了123,那么它是字符串的123还是整数的123还是引用的地址是123,这个它就可以知道
那么classic没有这种功能就会有一些问题,比如现在有一个对象在堆中要转移地址了,在classci中没法判断这个123到底是引用地址还是实际的类型,所以对象转换位置之后栈中的引用改变不了位置,只能通过再次寻找这个对象来得到新的位置,这样在无形之间就多了很大的开销。
现在多除了这个准确式内存管理,立刻就可以区分这个123是引用还是值,进而可以直接替换新地址
编译器和解释器混合工作模式上面已经讲过了,这里就不讲了
热点探测就是找到高频执行的代码,也就是我们刚才讲过的热点代码
Hotspot VM
至今仍在使用
最初不是Sun公司的产品,而是一家名为”Longview Techonlogies”的小公司设计。1997年这个公司被Sun收购,2009年,Sun被Oracle收购
JDK1.3时,HotSpot VM成为默认虚拟机
目前HotSpot占有绝对的市场地位,不管是JDK6还是JDK8,默认都是HotSpot
Sun/Oracle JDK和OpenJDK的默认虚拟机
我们学习,以HotSpot来进行默认的介绍,相关的机制也是主要介绍HotSpot的GC机制(比如说,其他的两个虚拟机都没有方法区的概念)
HotSpot的意思就是热点探测
- 通过计数器找到最具编译价值的代码,触发即时编译或者栈上替换
- 通过编译器与解释器的协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡
栈上替换的意思是:对象不一定都要创建在堆空间中,我们还可以在栈上去创建对象
JRockit VM
- 来自BEA
- 专注于服务端应用,因此*
不太关心程序的启动时间,没有了解释器
* - 大量的行业基准显示,*
JRockit JVM是世界上最快的JVM
*,使用JRockit产品,客户已经体验到了显著的性能提高(甚至超过了70%),硬件成本也有减少(能够达到50%) - 全面的Java运行时解决方案组合
- JRockit面向延迟敏感性应用的解决方案JRockit Real Time提供以毫秒或微秒级的JVM响应时间,适合财务,军事指挥,电信网络的需要
- MissionControl服务套件,它是一组以极低的开销来监控,管理和分析生产环境中的应用程序的工具
- 2008年BEA被Oracle收购
- Oracle表达了整合两大优秀虚拟机的工作,大致在JDK8中完成。整合的方式是在HotSpot的基础上,移植JRockit的优秀特性
J9 VM
- 来自IBM,全称:IBM Technology for Java Virtual Machine,简称IT4J,内部代号J9
- 市场定位与HotSpot接近,服务器端,桌面应用,嵌入式等多用途VM
- 广泛用于IBM的各种Java产品
- 目前是有影响力的三大商用组件之一(另外两个是HotSpot和JRockit),自我号称是世界上最快的Java虚拟机(其实不如JRockit)
- 2017年左右,IBM发布了开源J9 VM,命名为OpenJ9,交给Eclipse基金会管理,也称为Eclipse OpenJ9
注意OpenJ9和OpenJDK不一样,OpenJ9是VM层面的,而OpenJDK是JDK层面的
KVM和CDC/CLDC Hotsopt
- Oracle在Java ME产品线上的两款虚拟机为:CDC/CLDC Hotspot Implementation VM
- KVM(Kilobyte)是CLDC-HI早期产品
- 智能手机被Android和IOS占领,所以目前移动领域地位尴尬
- KVM简单轻量,高度可移植,面向更低端的设备上还维持自己的一片市场
- 智能控制器,传感器
- 老人手机,经济欠发达地区的功能手机
这是不太重要的一些虚拟机,仅作了解
Azul VM 和 Liquid VM
Azul VM
- 前面的三大高性能Java虚拟机(Hotspot,JRockit,J9)使用在通用硬件平台上,而这里的Azul VM和BEA Liquid VM是*
与特定硬件平台绑定
*,软硬件配合的专有虚拟机。因为与特定硬件进行绑定,所以是高性能虚拟机中的战斗机。硬件方面也十分耦合。 - Azul VM是Azul Systems公司在Hotspot基础上进行大量改进,运行于Azul Systems公司的专有硬件Vega系统上的虚拟机
每个Azul VM实力都可以管理至少数十个CPU和数百GB内存的硬件资源,并提供在巨大内存范围内实现可控的GC时间的垃圾收集器、专有硬件欧化的线程调度等优秀特性
- 2010年,Azul Systems公司开始从硬件转向软件,发布了自己的Zing JVM,可以在通用x86平台上提供接近于Vega系统的特性。
Liquid VM
- 高性能Java虚拟机的战斗机
- BEA开发的,直接运行在自家的Hypervisor系统上
- Liquid VM是现在的JRockit VE,*
Liquid VM不需要操作系统的支持,或者说它自己本身实现了一个专用的操作系统的必要功能,比如线程调度,文件系统,网络支持等
* - 随着JRockit虚拟机的开发,Liquid VM项目也停止了
Apache Harmony
- Apache也曾经推出过JDK1.5和JDK1.6兼容的Java运行平台Apache Harmony
- 它是IBM和Intel联合开发的开源JVM,收到同样开源的OpenJDK的压制,Sun坚决不让Harmony获得JCP认证,最终于2011年退役,,IBM转而参加OpenJDK
- 虽然目前并没有Apache Harmony被大规模商用的案例,但是它的Java类库代码吸纳进了Android SDK
Microsoft VM
- 微软在IE3浏览器中支持Java Applets,开发了Microsoft JVM
- 只能在windows平台下运行。但确实是当时Windows下性能最好的Java VM
- 1997年,Sun以侵犯商标,不正当竞争罪名指控微软成功,赔了sun很多钱,微软在WindowsXP Sp3抹掉了其VM。现在Windows上安装的JDK都是Hotspot
Taobao JVM
- 基于OpenJDK开发了AlibabaJDK,简称AJDK,是整个阿里Java体系的基石
- 基于OpenJDK HotSpot VM发布的国内第一个优化,深度定制并且开源的高性能服务器版Java虚拟机
- 创新的GCIH(GC invisible heap)技术实现了off-heap,即*
将生命周期较长的Java对象从heap中移到heap之外,并且GC不能管理GCIHJ内部的Java对象,以此来达到降低GC的回收频率和提升GC的回收效率的目的
* - GCIH中的对象还能*
够在多个Java虚拟机进程中实现共享
*
- 创新的GCIH(GC invisible heap)技术实现了off-heap,即*
- 在阿里产品上性能极高,硬件严重依赖于intel中的cpu,把Oracle官方JVM版本全部替换了
Dalvik VM
- 不是Java虚拟机(没有遵循Java虚拟机规范),但是是Andoird虚拟机
- 不能直接执行Java的Class文件
- 基于寄存器的架构
- 执行的是编译之后的dex文件
- Android5.0使用支持提前编译的ART VM替换Dalvik VM
Graal VM
- 2018年4月,Oracle Labs公开了Graal VM,号称 “Run Programs Faster Anywhere”
- Graal VM在HotSpot VM基础上增强而成的跨语言全栈虚拟机,可以作为任何语言的运行平台使用,包括Java,Scala,Groovy,Kotlin,C,C++,JavaScript,Ruby,Python,R等
- 支持不同语言中混用对方的接口和对象,支持这些语言使用已经编辑好的本地库文件
- 如果HotSpot有一天真的被取代,那么Graal VM的希望最大。但是Java的软件生态没有丝毫变化