JVM 是 Java Virtual Machine 的简称,Java 虚拟机。虚拟机是指通过软件模拟具有完整硬件功能、运行在一个完全隔离的环境中的完整计算机系统。JVM 是主流虚拟机,其他常见的虚拟机还有:VMwave, Virtual Box.
2. JVM 执行流程JVM 是 Java 运行的基础,也是实现一次编译到处执行的关键。一个 java 文件从编译到执行需要经过以下四个阶段。
- 编译阶段:程序在执行前要先把java代码(java 文件)转换成字节码(class 文件);
- 加载阶段:JVM 把 class 文件经过类加载器把文件加载到内存中运行时数据区;
- 解释阶段:因为字节码文件是 JVM 的一套指令集规范,并不能直接让底层操作系统执行,因此需要特定的解释器执行引擎进行翻译,将字节码解释成系统可识别的底层系统指令码,而这个过程需要调用其他语言的接口也就是本地库接口来实现;
- 执行阶段:系统向硬件设备发送指令码执行操作。
JVM 结构图:
总结来看,JVM 主要通过四个部分来执行程序。分别是:
类加载器(ClassLoader)、运行时数据区(Runntime Data Area)、执行引擎(Execution Engine)、本地库接口(Native Interface)
类的生命周期:
从图中可以看出前五步是类加载的过程,中间三步合称为连接,所以类加载的过程分为:
- 加载
- 连接
2.1 验证
2.2 准备
2.3 解析 - 初始化
加载是一个类生命周期中的加载阶段不是类加载的一个阶段,两者是不同的,不要混淆。在加载阶段,java虚拟机需要完成三件事:
- 通过一个类的全限定名(包名 + 类名)来获取定义此类的二进制字节流;
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
-
验证
验证是连接阶段的第一步,这一阶段的目的是确保 class 文件的字节流中包含的信息符合《java 虚拟机规范》的全部约束要求。
包括:文件格式验证、字节码验证、符号引用验证… -
准备
准备阶段是为类中定义的变量分配内存并设置类变量初始值的阶段。
比如 java 代码中的:public static int value = 12;
初始化 value 的 int 值为0. -
解析
解析阶段是 java 虚拟机将常量池内的符号引用替换为直接引用(将常量池中的变量转化为对象)的过程,也就是初始化常量的过程。
初始化阶段就是执行类构造器方法的过程,就是 java 虚拟机真正开始执行类中的 java 程序代码。
3.2 双亲委派模型3.2.1 什么是双亲委派模型从 java 虚拟机角度看,只存在两种不同的类加载器:一种是启动类加载器,这个类加载器使用 C++
语言实现,是虚拟机自身的一部分;另一种是其他所有的类加载器,这些类独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader.
双亲委派模型就是:如果一个类加载器收到了类加载的请求,它不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层加载器都是将这个请求委派给父类加载器去完成,直到所有的加载请求最终都传送到最顶层的启动类加载器中。只有当父加载器无法而完成这个加载请求时,子类加载器才会尝试自己去完成加载。
加载过程图:
- 启动类加载器:加载 JDK 中 lib 目录中 Java 的核心类库。
- 扩展类加载器:加载 lib/ext 目录下的类。
- 应用程序类加载器:加载自己写的应用程序。
- 自定义类加载器:根据自己的需求定制类加载器。
1.避免重复加载类:当两个类的父类相同时,启动其中一个类就会加载父类,那么另一个类加载时父类就不会加载了;
2.安全性:使用双亲委派模型启动类加载时只会加载JDK 中 lib 目录中 Java 的核心类库,这个类是不会被随意替换的,就可以避免有人自定义一个有破坏功能的类保证 java 的核心 API 不被篡改。
在某些情况下需要破坏这个模型,所以双亲委派模型不是必然的。比如 Java 中 SPI 机制中的 JDBC 的实现,或者是 tomcat 中的 servlet 加载。tomcat 中的 类加载机制:为了给每个 web 容器单独提供一个 WebAppClassLoader 加载器,优先加载 web 应用自己定义的类也就是交给子类加载器加载的,这样就破坏了遵守双亲委派模型。
4. 运行时数据区JVM 运行时数据区也叫内存布局,但是它和 Java 内存模型(Java Memory Model, 简称 JMM)不同。
JVM 运行时数据区构成图:
-
方法区:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是线程共享的。在 HotSpot 虚拟机的实现中,在 jdk 7中此区域叫做永久代,jdk 8中叫做元空间。
-
虚拟机栈(栈内存,线程私有) java 虚拟机栈描述的是 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址、附加信息等信息。
-
本地方法栈:和虚拟机栈类似,但本地方法栈是给本地方法使用的。是线程私有的。
-
堆:程序中创建的所有对象都保存在堆中。堆里面分为两个区域:新生代和老生代。新生代放新建的对象,当经过一定 GC 次数之后还存活的对象会放入老生代。
-
程序计数器:用来记录当前线程执行的行号。如果当前线程正在执行的是一个 java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个
Native 方法,这个计数器值为空。程序计数器内存区域是唯一一个在 JVM 规范中没有规定任何 OOM 情况的区域!



