前言
JVM
是 Java Virtual Machine
( Java 虚拟机)的缩写,JVM
是一种用于计算设备的规范,它是一个虚构的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
JVM屏蔽了与具体操作系统平台相关的信息,使 Java 程序只需生成在 Java 虚拟机上一次编译,多次运行(write once, run anywhere),具有跨平台性。JVM
在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
JDK、JRE和JVM对比
JDK
JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。JDK
是 programming tools
、JRE
和 JVM
的一个集合。
JRE
JRE
(Java Runtime Environment)Java
运行时环境,JRE
是物理存在的,主要由 Java API
和 JVM
组成,提供了用于执行 Java 应用程序最低要求的环境。
JVM
JVM
是一种用于计算设备的规范,它是一个虚构的计算机的软件实现,简单的说,JVM
是运行字节码(Byte Code)程序的一个容器。
HotSpot VM
HotSpot VM 是 Sun JDK 和 OpenJDK 中所带的虚拟机,也是目前使用范围最广的 Java 虚拟机。
JVM 的特点
- 基于堆栈的虚拟机:最流行的计算机体系结构,如英特尔
X86
架构和ARM
架构上运行基于 寄存器。比如,安卓的Davilk
虚拟机就是基于 寄存器 结构,而JVM
是基于栈结构的。 - 符号引用(symbolic references) :除了基本类型以外的数据 (比如类和接口) ,都是通过符号来引用,而不是通过显式地使用内存地址来引用。
- 垃圾收集 :一个类的实例是由用户程序创建和垃圾回收自动销毁。
- 网络字节顺序 :
Java class
文件用网络字节码顺序来进行存储,保证了小端的Intel x86
架构和大端的RISC
系列的架构之间的无关性。
JVM 字节码(JVM Byte Code)
JVM
使用 Java 字节码的方式,作为 Java 用户语言 和 机器语言 之间的中间语言。实现一个通用的、机器无关 的执行平台。
类文件格式
有趣的是,其实JVM并不关心Java语言或其他编程语言的语义和语法结构。当JVM执行一段程序的时候,它主要关注的是一种称为“类文件”的特定文件格式。
.class 类文件格式和 Java 代码定义的面向对象的类结构毫无关系。编译器将 .java 文件编译成 .class 文件,然后 JVM 对*.class文件进行解译,它不关心这个类文件是由哪种编译器生成的,只要符合类文件的文件格式即可。
Java编译器将一段程序编译为等价的类文件。这些类文件实际上包含了半编译的代码——字节码(byte code)。
之所以称之为半编译,是因为字节码并不像C/C++编译器编译的与机器指令相关的二进制文件一样可以被被直接执行。
字节码要先被输入到 JVM 中,然后再转换为底层平台可以执行的最终指令。所以字节码包含了JVM的指令、符号表和其他的辅助信息。不管何种语言,能根据JVM的语法和结构约束编译生成字节码的编译器,都是一个可以在JVM上执行的候选者。
JVM的定位
JVM将自身定位于字节码和底层平台之间。底层平台是指操作系统(OS)和硬件。
操作系统和硬件体系结构在不同的机器上可能不同,但是同一段Java程序可以不用做任何的代码修改就能在不同的机器上运行。这是在虚拟环境中执行的程序语言的独特之处。
例如,由其他程序语言编译器编译的目标代码如C++和Java相比的不同点在于,C++程序需要被特定平台的编译器重新编译,从而使它能在不同的体系结构上面运行。而Java代码并不需要做任何改变,因为由Java编译器编译的字节码是在外围的JVM上执行。
因此,JVM负责重新解译由Java编译器生成的字节码,并和底层平台协调工作。也就是说,尽管Java编译器生成的结果是平台独立的,但JVM与特定平台相关的。除非两台机器有相同的体系结构,在某个体系结构上安装和使用的 JVM 当被直接复制到另一台机器时,可能就不能正常工作了。
JVM 实例的生命周期
- 启动:任何一个拥有
main
方法的class
都可以作为JVM
实例运行的起点。 - 运行:
main
函数为起点,程序中的其他线程均有它启动,包括daemon
守护线程和non-daemon
普通线程。daemon
是JVM
自己使用的线程比如GC
线程,main
方法的初始线程是non-daemon
。 - 消亡:所有线程终止时,
JVM
实例结束生命。
在JVM上执行Java程序
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
每一个在JRE上运行的Java程序都会创建一个JVM实例。编译后的Java类文件和其他被依赖的类文件会被加载到运行环境中。这一步由类加载器(class loaders)协助完成。
首先,类加载器(class loaders)会以字节码(byte code)的形式,加载程序类文件和与JDK绑定的标准 Java 类文件。标准类文件构成了Java API核心类库。引导程序通过定位通常位于jre/lib目录下的核心API类库启动。
然后,扩展机制定位扩展类库,例如一些为开发或执行代码而被添加到Java里新的(可选)包。扩展类通常位于 jre/lib/ext目录下。有时,扩展类会被放到系统属性java.ext.dirs 定义的其他目录下面。程序包使用JAR或ZIP的扩展名。
最后,如果要加载的类没有在Java的标准类库或扩展类库中被找到,加载器会搜索CLASSPATH环境变量下定义的文件路径,CLASSPATH里面包含了诸多存储类文件的地址。系统属性java.class.path对CLASSPATH环境变量做了映射。
像JAR或ZIP这样的归档文件都是包含了一些其他文件目录的独立文件,通常是压缩文件格式。例如,程序中使用的标准类库包含在归档文件 rt.jar中,该文件会和JDK被一同安装。
一旦文件被定位并加载之后,类加载器会执行不同的功能,例如根据JVM的约束进行校验、内存分配,或者在调用构造器设置定义的变量元素之前使用默认值初始化类变量。
当加载程序结束之后,字节码指令被传递给执行引擎。然后JVM借助于绑定到指定平台的特定JVM实现的本地代码和底层操作系统进行交互。请注意,不同平台的实现可能有略微不同。
Reference
-
《深入理解Java虚拟机:JVM高级特性与最佳实践》
-
JVM Architecture – Understanding JVM Internals - https://www.javainterviewpoint.com/java-virtual-machine-architecture-in-java/#respond
-
JVM(一)史上最佳入门指南 - https://www.imooc.com/article/272234
-
JVM基础面试题及原理讲解 - http://www.importnew.com/31126.html
-
JVM系列(一) - JVM总体概述 - https://juejin.im/post/5b4de8185188251af86be259
-
Java虚拟机(JVM)概述 - http://www.importnew.com/29224.html
-
可能是把Java内存区域讲的最清楚的一篇文章 - https://juejin.im/post/5b7d69e4e51d4538ca5730cb
-
深入理解JVM类加载机制 - https://juejin.im/post/5a1d5f286fb9a045132a7100
-
Java虚拟机 —— 类的加载机制 - https://juejin.im/post/59c4dd9e5188257e876a1aee
-
Jvm原理入门 - https://leeyuan.cf/2018/02/03/Jvm%E5%8E%9F%E7%90%86%E5%85%A5%E9%97%A8/