类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。
包含了加载、验证、准备、解析和初始化这 5 个阶段。
加载过程完成以下三件事:
其中二进制字节流可以从以下方式中获取:
JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。
cafe bene
开头)。JVM 会在该阶段对类变量(也称为静态变量,static
关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。
此时不会分配实例变量的内存,因为实例变量是在实例化对象时一起创建在Java 堆中的。而且此时类变量是赋值为零值,即 int 类型的零值为 0,引用类型零值为 null,而不是代码中显示赋值的数值。
该阶段将常量池中的符号引用转化为直接引用。
在 class 文件中常量池里面存放了字面量和符号引用,符号引用包括类和接口的全限定名以及字段和方法的名称与描述符。
在 JVM 动态链接的时候需要根据这些符号引用来转换为直接引用存放内存使用。
该阶段是类加载过程的最后一步。在准备阶段,类变量已经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码期望赋的值。换句话说,初始化阶段是执行类构造器方法的过程。
讲到类加载不得不讲到类加载的顺序和类加载器。
Java 中大概有四种类加载器,分别是:启动类加载器(Bootstrap ClassLoader),扩展类加载器(Extension ClassLoader),系统类加载器(System ClassLoader),自定义类加载器(Custom ClassLoader),依次属于继承关系(注意这里的继承不是 Java 类里面的 extends)
类加载器在加载 class 文件的时候,遵从双亲委派原则,意思是加载依次由父加载器先执行加载动作,只有当父加载器没有加载到 class 文件时才由子类加载器进行加载。这种机制很好的保证了 Java API 的安全性,使得 JDK 的代码不会被篡改。
1、创建一个 Java 源文件 Test02.java,并在 main 方法中完成简单的逻辑操作,如下所示。
public class Test02 {
public static void main(String[] args) {
int i = 5;
int j = 10;
int k = i + j;
System.out.println(k);
}
}
2、在终端通过 javac 命令编译 HelloWorld.java。
javac Test02.java
3、反编译成我们能看懂的 JVM 指令,这里我们使用 javap -c 命令完成。
javap -c Test02.class
4、反编译之后的 JVM 指令如下所示。
1 Compiled from "Test02.java"
2 public class org.example.jvm.Test02 {
3 public org.example.jvm.Test02();
4 Code:
5 0: aload_0
6 1: invokespecial #1 // Method java/lang/Object."":()V
7 4: return
8
9 public static void main(java.lang.String[]);
10 Code:
11 0: iconst_5
12 1: istore_1
13 2: bipush 10
14 4: istore_2
15 5: iload_1
16 6: iload_2
17 7: iadd
18 8: istore_3
19 9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
20 12: iload_3
21 13: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
22 16: return
}
解释一下上述的 JVM 指令:
第 1 行表示当前的字节码文件编译自 Test02.java
。
第 3 行表示调用 Test02
的无参构造函数来实例化当前对象。
第 4 行到第 7 行表示无参构造函数的执行流程。
第 5 行表示把 this 压入操作数栈中。
第 6 行表示调用 Test02父类 Object 的无参构造,我们知道每个对象在实例化的时候都会默认先实例化其父类对象,并且默认调用父类的无参构造。
第 7 行 return 表示构造方法执行完毕。
第 10 行到第 22 行表示 main 方法的执行流程。
第 11 行表示将常量 5 压入操作数栈。
第 12 行表示取出操作数栈栈顶元素,即 5,然后保存到局部变量表第 1 个位置,即变量 i。
第 13 行表示将常量 10 压入操作数栈。
第 14 行表示取出操作数栈栈顶元素,即 10,然后保存到局部变量表第 2 个位置,即变量 j。
第 15 行表示将局部变量表第 1 个变量(i)压入操作数栈。
第 16 行表示将局部变量表第 2 个变量(j)压入操作数栈。
第 17 行表示取出操作数栈中的前两个值相加,并将结果压入操作数栈顶。
第 18 行表示取出操作数栈栈顶元素,保存到局部变量表第 3 个位置,即变量 k。
第 19 行表示读取静态实例 PrintStream。
第 20 行表示将局部变量表第 3 个变量(k)压入操作数栈。
第 21 行表示调用 PrintStream 的 println 方法,将操作数栈顶元素(变量 k)输出。
第 22 行 return 表示 main 方法执行完毕。