JVM(一)----- 类加载过程
一、什么是类加载(Class Loading)?
在 Java 程序运行时,类(.class 文件)并不是一次性全部加载进 JVM 的,而是 按需动态加载 的。这个过程由 类加载器(ClassLoader) 完成(我们应该知道类加载器的结构,是各自完成自己负责的类的加载任务),它的任务是:
把类的字节码文件加载到 JVM 内存中,并在方法区(元空间)中创建对应的类元数据结构。
二、类加载的七个阶段(从加载到使用)
JVM 规范定义了类的完整生命周期:
加载(Loading)
→ 连接(Linking)
├ 验证(Verification)
├ 准备(Preparation)
└ 解析(Resolution)
→ 初始化(Initialization)
→ 使用(Using)
→ 卸载(Unloading)
我们主要分析前四步,因为 它们决定类是如何进入内存、被准备好使用的。
① 加载(Loading)
目标:
-
通过类的全限定名(如
java.lang.String)获取其字节码文件; -
将字节码读入内存;
-
在 元空间(Metaspace) 中为该类生成对应的 类元数据结构(Class对象的内部描述);
-
生成一个
java.lang.Class实例(代表该类的“镜像”)。
加载的来源可能是:
-
本地文件系统(
.class或.jar) -
网络(如 Web Start)
-
动态生成(如反射、动态代理、ASM 字节码)
类加载器(ClassLoader)机制:
-
启动类加载器(BootstrapClassLoader):加载 JDK 核心类库(
rt.jar/java.base)。 -
扩展类加载器(ExtClassLoader):加载
jre/lib/ext目录下的类。 -
应用类加载器(AppClassLoader):加载
classpath下的应用类。 -
自定义类加载器:由开发者自定义加载路径或加密逻辑。
双亲委派模型:
加载请求会先向上委派,父加载器若无法加载,才由子加载器尝试,防止核心类被篡改。
此时开始涉及元空间(Metaspace)!
类加载器加载类的字节码文件的过程中,将这个类的相关信息放入元空间
-
类的元数据(字段表、方法表、常量池等)被加载到 元空间(方法区实现);
-
但类的静态变量等数据还未初始化。(在准备阶段赋值)
② 连接(Linking)
连接阶段将类加载后的字节码转为可执行的 JVM 运行时结构。
(1)验证(Verification)(先保证安全)
确保字节码合法、安全、不破坏 JVM。
-
格式验证:魔数、版本号是否正确;
-
元数据验证:父类存在吗?接口合法吗?
-
字节码验证:方法体的控制流、操作数栈是否合法;
-
符号引用验证:常量池中的引用类型正确。
如果验证失败,会抛出 VerifyError。
(2)准备(Preparation)
为类的 静态变量分配内存并设为默认值(不执行赋值语句)
这些静态变量存放在 Java 堆(Heap) 或 元空间的静态区域中(取决于实现),
但变量的“定义信息”仍在元空间。
(3)解析(Resolution)
把常量池中的 符号引用转换为 直接引用(真正与虚拟机内存关联起来)。
③ 初始化(Initialization)
真正执行类的 <clinit>() 方法(类初始化块 + 静态变量初始化)。
前面准备的时候为静态变量赋默认值,但是现在就是真正执行java代码了:
public static int a = 10;
static {a = 20;
}
这个阶段执行的就是:
-
静态变量赋值语句;
-
static {}静态代码块; -
初始化顺序:父类先于子类。
只有初始化阶段才真正执行 Java 代码(前面阶段都不执行代码)。
④ 使用(Using)
类初始化完成后,就可以:
-
创建实例;
-
调用静态方法;
-
访问静态变量;
-
进行反射操作;
-
被其他类继承。
⑤ 卸载(Unloading)
当:
-
该类对应的
ClassLoader被 GC 回收; -
且没有该类的实例对象存在;
-
且没有该类的
Class对象引用;
则该类的元数据会从 元空间(Metaspace) 中释放。
最后,类的相关信息(类结构、方法表、常量池等)都被存储在元空间中,但是类的对象存储在java堆中。
细节如下:
-
加载阶段 将类的元数据加载进 元空间(Metaspace);
-
验证-准备-解析;
-
初始化阶段 才真正执行静态代码;
-
类的元信息(字段表、方法表等)存放在 Metaspace;
-
之后就可以使用这个类了,创建类的实例,通过实例调用方法,但是要知道类对象在堆中;
-
类的实例对象存放在 Heap;
-
类被卸载时,对应的 元空间内存 也会被释放。
