虚拟机就是一款用来执行虚拟计算机指令的计算机软件。它相当于一台虚拟计算机。大体上,虚拟机分为系统虚拟机和程序虚拟机。系统虚拟机就相当于一台物理电脑,里面可以安装操作系统;程序虚拟机是为了执行单个计算机程序而设计出来的虚拟机。其中 Java 虚拟机就是执行 Java 字节码指令的虚拟机。
java 虚拟机是运行在各大平台的执行字节码文件的虚拟计算机。如下图所示
这样的设计可以让编译后的代码在各个操作平台上运行。
JVM 在 Java 体系结构中的位置
从用户操作角度看 JVM 所处的位置
JVM 与实际的计算机硬件没有交互,它们中间还有个操作系统,调用硬件需要通过操作系统来实现。
JVM 的整体运行结构
本文主要针对于 Hotspot VM 来进行,其结构如下所示:
JVM 的指令结构
JVM 是基于栈的指令集架构,跟基于寄存器的指令集不同。它是一个步骤一个一条指令。
//基于栈的指令集
iconst_2 //常量2入栈
istore_1
iconst_3 // 常量3入栈
istore_2
iload_1
iload_2
iadd //常量2/3出栈,执行相加
istore_0 // 结果5入栈
//基于寄存器的指令集
mov eax,2 //在eax寄存器中的值设为2
add eax,3 //将eax寄存器中的值加3
举例说明:
/**
* @author wjw
* @date 11/3/2021
*/
public class StackStruTest {
public static void main(String[] args) {
int i = 2 + 3;
/**
*0: iconst_5
*1: istore_1
*2: return
*/
int i = 2;
int j = 3;
int k = i + j;
/**
* 0: iconst_2
* 1: istore_1
* 2: iconst_3
* 3: istore_2
* 4: iload_1
* 5: iload_2
* 6: iadd
* 7: istore_3
* 8: return
*/
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
}
JVM 的启动
Java 的启动是通过引导类加载器(BootStrap ClassLoader)创建一个初始类(java.lang.class)来实现的,也就是后面要提到的子类加载器过程。
JVM 的执行
执行 Java 的字节码文件中的各个方法和类的过程。
JVM 的退出
让我们先看看一个 java 程序执行的流程是怎样的:
java.lang.class
对象,这个对象将作为程序访问方法区中的这些类型数据的外部接口。而类加载子系统涉及到的过程有类加载器、字节码校验器和翻译字节码这几个过程:
加载阶段(Loading)是类加载阶段(Class Loading)的一部分。在加载阶段中,.class 文件将经过三个类加载器的加载后才能进入下一个阶段。该阶段主要的作用(也就是上面 java 程序执行中的类加载器阶段)有:
在类加载器中,主要分为两类:引导类加载器和自定义类加载器
派生于抽象类 ClassLoader 的类加载器
为了防止虚拟机内部的核心类库的修改和非法调用,JVM 的设计者们在类加载过程中设计出了双亲委派机制和沙箱安全机制。
举例
双亲委派机制的优点:
引导类加载器在加载时会首先加载 JDK 中自带的文件。报错信息中没有相应方法时的机制。保证对 java 核心源代码的保护。
验证字节流文件中的相关信息,确认当前文件符合虚拟机的相关格式(文件、元数据、符号等等)。
在方法区中为类变量分配内存并设置类变量初始值
初始化过程实际上就是执行类构造器方法 ( ) 的过程
public class ClassInitTest {
private static int num = 1;
static {
num = 2;
number = 20;
System.out.println(num);//因为在linking 阶段中的 Prepare 阶段已经给静态变量赋过初始值了。
//非法的前向引用;在声明变量之前,可以去给这些变量进行赋值,但是不能够调用它。
//System.out.println(number);//会报错误
}
/**也就是在 linking 中的 prepare 中对变量进行默认赋值为0
* 此时initial 给予其一个覆盖从 20 到 10
*
*/
private static int number = 10;
public static void main(StringDemo[] args)
{
System.out.println(ClassInitTest.num);
System.out.println(ClassInitTest.number);
}
}