JVM整体结构
字节码
Java虚拟机是解释运行字节码文件的,如下图其他语言只要通过其编译器生成了对应字节码文件就可以在java虚拟机上运行。
-
我们平时说的java字节码,指的是用java语言编译成的字节码。准确的说任何能在jvm平台上执行的字节码格式都是一样的。所以应该统称为:jvm字节码。
-
不同的编译器,可以编译出相同的字节码文件,字节码文件也可以在不同的JVM上运行。
-
Java虚拟机与Java语言并没有必然的联系,它只与特定的二进制文件格式——Class文件格式所关联,Class文件中包含了Java虚拟机指令集(或者称为字节码、Bytecodes)和符号表,还有一些其他辅助信息。
虚拟机
所谓虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。也就是虚拟机是模拟我们物理的计算机
大体上,虚拟机可以分为系统虚拟机和程序虚拟机:
- 系统虚拟机
Virtual Box,VMware就属于系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台 - 程序虚拟机
程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令。
JAVA虚拟机
-
Java虚拟机就是二进制字节码的运行环境
这里运行的字节码文件可以不是java语言编译而成的 -
Java虚拟机的特点
- 一次编译,到处运行
- 自动内存管理
- 自动垃圾回收功能
-
JVM的位置
JVM是运行在操作系统之上的,它与硬件没有直接的交互
-
java的体系结构
我们想一个java程序运行成功需要两个过程:
-
前端编译器
.java文件编译成.class字节码文件,这里用到的编译器称为前端编译器,如javac。最上面的蓝色部分Tools&Tool APIs就是关于这部分的编译器 -
将字节码文件解释运行
jre是java运行时环境,它包含了一些类库的class文件和java虚拟机。这部分就是对字节码文件解释运行,运行过程还涉及到后端编译器,后端编译器就是jvm层面的内容
-
JVM整体结构
下图是HotSpotVM的整体结构,它采用解释器与即时编译器并存的架构。
- 类装载器子系统
类装载器子系统主要作用就是将字节码文件加载到内存中,生成一个Class对象。
这个过程会涉及到加载、链接、初始化 - 运行时数据区
运行时数据区就是内存中的一些结构。
在内存中,多个线程共享方法区和堆。
java栈、本地方法栈和程序计数器是每个线程独有的 - 执行引擎
执行引擎包含三部分:解释器,即时编译器,垃圾回收器
因为操作系统只能够识别机器指令,执行引擎相当于就把字节码文件翻译成操作系统能够识别的指令。
JAVA代码执行流程
JAVA代码执行流程可以概括为下:
Java程序通过编译生成一个或多个字节码文件。(每个字节码文件对应一个java层面的类)
详细流程图如下:
- Java编译器
这部分将Java源码编译成字节码文件,这中间任何一个环节失败了都不能正确生成字节码文件 - Java虚拟机
这部分就是字节码文件先过类加载器,再到字节码校验器,再到执行引擎。- 类加载器
- 字节码校验器
- 执行引擎
- 翻译字节码(解释执行)
主要是来保证响应时间 - JIT 编译器(编译执行)
把字节码中的字节码指令编译成机器指令(这里就是二次编译),同时把这个机器指令缓存起来(因为是反复执行的热点代码)
- 翻译字节码(解释执行)
JVM架构模型
- JAVA编译器输入的指令流是基于栈的指令集架构
- JAVA编译器使用基于栈的指令集架构的原因
由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。 - HotSpot VM的宿主环境已经不局限于嵌入式平台了,不将架构更换为基于寄存器架构的原因
因为基于栈的架构在设计和实现上更简单,在非资源受限的场景当中也是可以用的,所以也没必要更换了。(因为基于栈的架构跨平台性好、指令集小,虽然相对于基于寄存器的架构来说,基于栈的架构编译得到的指令更多,执行性能也不如基于寄存器的架构好,但考虑到其跨平台性与移植性,我们还是选用栈的架构)
指令集架构
指令集架构可以分为基于栈的指令集架构和基于寄存器的指令级架构两种
基于栈的指令集架构
基于栈式架构的特点:
- 设计和实现更简单,适用于资源受限的系统;
- 避开了寄存器的分配难题:使用零地址指令方式分配
零地址指令就是没有地址,只有操作数。因为栈只是入栈出栈 只对栈顶进行操作,所以不需要地址。 - 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现
- 不需要硬件支持,可移植性更好,更好实现跨平台
基于寄存器的指令级架构
典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虚拟机。
- 缺点:
指令集架构则完全依赖硬件,与硬件的耦合度高,可移植性差 - 优点
- 性能优秀和执行更高效
- 花费更少的指令去完成一项操作
两架构对比距离
在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主
基于寄存器架构的指令少,但指令集多(因为是16位的);基于栈的指令集架构指令集小(因为是8位的),但指令多。
同样执行2+3这种逻辑操作,其指令分别如下:
-
基于栈的计算流程(以Java虚拟机为例):
iconst_2 //常量2入栈 istore_1 iconst_3 // 常量3入栈 istore_2 iload_1 iload_2 iadd //常量2/3出栈,执行相加 istore_0 // 结果5入栈
-
而基于寄存器的计算流程
mov eax,2 //将eax寄存器的值设为1 add eax,3 //使eax寄存器的值加3
JVM 生命周期
虚拟机的启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。
虚拟机的执行
- 执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程
- 程序开始执行时虚拟机才运行,程序结束时虚拟机就停止
- 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序
虚拟机的退出
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 由于操作系统用现错误而导致Java虚拟机进程终止
- 某线程调用Runtime类或System类的exit()方法,或Runtime类的halt()方法,并且Java安全管理器也允许这次exit()或halt()操作。
调用 System.exit() 方法 --> 调用 Runtime.exit() 方法
调用 Runtime.exit() 方法 --> 调用了 ShutDown.exit() 方法 - 除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载 Java虚拟机时,Java虚拟机的退出情况。
Runtime
Runtime 对象全局唯一,对应着运行时数据区
Runtime类:
明显的饿汉设计模式:一上来就 new 了一个 Runtime 类的实例,并且将 Runtime 类的构造器私有化了
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
Runtime 类的 exit() 方法调用了 ShutDown 类的 exit() 方法
Runtime的exit()方法:
ShutDown 类
ShutDown的exit() 方法会调用到本地方法 runAllFinalizers() 和 halt0()
static void exit(int status) {
boolean runMoreFinalizers = false;
synchronized (lock) {
if (status != 0) runFinalizersOnExit = false;
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and halt */
break;
case FINALIZERS:
if (status != 0) {
/* Halt immediately on nonzero status */
halt(status);
} else {
/* Compatibility with old behavior:
* Run more finalizers and then halt
*/
runMoreFinalizers = runFinalizersOnExit;
}
break;
}
}
if (runMoreFinalizers) {
runAllFinalizers();
halt(status);
}
synchronized (Shutdown.class) {
/* Synchronize on the class object, causing any other thread
* that attempts to initiate shutdown to stall indefinitely
*/
sequence();
halt(status);
}
}
static void halt(int status) {
synchronized (haltLock) {
halt0(status);
}
}
static native void halt0(int status);
/* Wormhole for invoking java.lang.ref.Finalizer.runAllFinalizers */
private static native void runAllFinalizers();
System 类源码
System类的exit()方法:
JVM发展历程
- Sun Classic VM和Exact VM是HotSpot VM的前身
- JRockit目前有影响力的三大商用虚拟机就是HotSpot VM、JRockit和IBM的J9,这三个是在通用硬件平台上。
- Azul VM和Liquid VM 是与特定硬件平台绑定、软硬件配合的专有虚拟机,所以是高性能Java虚拟机中的战斗机
Sun Classic VM
- 早在1996年Java1.0版本的时候,Sun公司发布了一款名为sun classic VM的Java虚拟机,它同时也是世界上第一款商用Java虚拟机,JDK1.4时完全被淘汰。
- 这款虚拟机内部只提供解释器,没有即时编译器,因此效率比较低,即时编译器会把热点代码缓存起来,那么以后使用热点代码的时候,效率就比较高。
- 如果使用JIT编译器,就需要进行外挂。但是一旦使用了JIT编译器,JIT就会接管虚拟机的执行系统。解释器就不再工作。解释器和编译器不能配合工作。
- 现在Hotspot内置了此虚拟机。
Exact VM
- 为了解决Sun Classic VM的问题,jdk1.2时,Sun提供了此虚拟机。
- Exact Memory Management:准确式内存管理
- 也可以叫Non-Conservative/Accurate Memory Management
- 虚拟机可以知道内存中某个位置的数据具体是什么类型。
- 具备现代高性能虚拟机的维形
- 热点探测(寻找出热点代码进行缓存)
- 编译器与解释器混合工作模式
- 只在Solaris平台短暂使用,其他平台上还是classic vm,英雄气短,终被Hotspot虚拟机替换
HotSpot VM
- HotSpot历史
- 最初由一家名为“Longview Technologies”的小公司设计
- 1997年,此公司被Sun收购;2009年,Sun公司被甲骨文收购。
- JDK1.3时,HotSpot VM成为默认虚拟机
- 目前Hotspot占有绝对的市场地位,称霸武林。
- 不管是现在仍在广泛使用的JDK6,还是使用比例较多的JDK8中,默认的虚拟机都是HotSpot
- Sun/oracle JDK和openJDK的默认虚拟机
- 因此本课程中默认介绍的虚拟机都是HotSpot,相关机制也主要是指HotSpot的GC机制。(比如其他两个商用虚机都没有方法区的概念)
- 从服务器、桌面到移动端、嵌入式都有应用。
- 名称中的HotSpot指的就是它的热点代码探测技术。
- 通过计数器找到最具编译价值代码,触发即时编译或栈上替换
- 通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡
BEA 的JRockit
- 专注于服务器端应用
因为专注于服务器端,所以不太关注程序启动速度,因此JRockit内部不包含解析器实现,只有即时编译器。 - JRockit JVM是世界上最快的JVM
- 优势:全面的Java运行时解决方案组合
- JRockit面向延迟敏感型应用的解决方案JRockit Real Time提供以毫秒或微秒级的JVM响应时间,适合财务、军事指挥、电信网络的需要
- Mission Control服务套件,它是一组以极低的开销来监控、管理和分析生产环境中的应用程序的工具。
这个Mission Control服务套件 是Oracle一直想加到HotSpot中的
IBM的J9
市场定位与HotSpot接近,服务器端、桌面应用、嵌入式等多用途VM广泛用于IBM的各种Java产品。
KVM和CDC / CLDC Hotspot
- Oracle在Java ME产品线上的两款虚拟机为:CDC/CLDC HotSpot Implementation VM KVM(Kilobyte)是CLDC-HI早期产品目前移动领域地位尴尬,智能机被Android和iOS二分天下。
- KVM简单、轻量、高度可移植,面向更低端的设备上还维持自己的一片市场
- 智能控制器、传感器
- 老人手机、经济欠发达地区的功能手机
- 所有的虚拟机的原则:一次编译,到处运行。
Azul VM
- 前面三大“高性能Java虚拟机”使用在通用硬件平台上这里Azul VW和BEA Liquid VM是与特定硬件平台绑定、软硬件配合的专有虚拟机:高性能Java虚拟机中的战斗机。
- Azul VM是Azul Systems公司运行于Azul Systems公司的专有硬件Vega系统上的Java虚拟机。
- 每个Azul VM实例都可以管理至少数十个CPU和数百GB内存的硬件资源,并提供在巨大内存范围内实现可控的GC时间的垃圾收集器、专有硬件优化的线程调度等优秀特性。
Liquid VM
- 高性能Java虚拟机中的战斗机。
- BEA公司开发的,直接运行在自家Hypervisor系统上
- Liquid VM即是现在的JRockit VE(Virtual Edition)
- Liquid VM不需要操作系统的支持,或者说它自己本身实现了一个专用操作系统的必要功能,如线程调度、文件系统、网络支持等。
- 随着JRockit虚拟机终止开发,Liquid vM项目也停止了。
Taobao JVM
- 由AliJVM团队发布。
- 基于OpenJDK开发了自己的定制版本AlibabaJDK,简称AJDK。是整个阿里Java体系的基石。
- 基于OpenJDK Hotspot VM发布的国内第一个优化、深度定制且开源的高性能服务器版Java虚拟机。
- 创新的GCIH(GCinvisible heap)技术实现了off-heap,即将生命周期较长的Java对象从heap中移到heap之外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升GC的回收效率的目的。
- GCIH中的对象还能够在多个Java虚拟机进程中实现共享
- 使用crc32指令实现JvM intrinsic降低JNI的调用开销
- PMU hardware的Java profiling tool和诊断协助功能
- 针对大数据场景的ZenGC
- taobao vm应用在阿里产品上性能高,硬件严重依赖inte1的cpu,损失了兼容性,但提高了性能
- 目前已经在淘宝、天猫上线,把Oracle官方JvM版本全部替换了。
Graal VM
- 2018年4月,Oracle Labs公开了GraalvM,号称 “Run Programs Faster Anywhere”,勃勃野心。与1995年java的”write once,run anywhere"遥相呼应。
- GraalVM在HotSpot VM基础上增强而成的跨语言全栈虚拟机,可以作为“任何语言”的运行平台使用。语言包括:Java、Scala、Groovy、Kotlin;C、C++、Javascript、Ruby、Python、R等
- 支持不同语言中混用对方的接口和对象,支持这些语言使用已经编写好的本地库文件
- 工作原理是将这些语言的源代码或源代码编译后的中间格式,通过解释器转换为能被Graal VM接受的中间表示。Graal VM提供Truffle工具集快速构建面向一种新语言的解释器。在运行时还能进行即时编译优化,获得比原生编译器更优秀的执行效率。
- 如果说HotSpot有一天真的被取代,Graalvm希望最大。但是Java的软件生态没有丝毫变化。