类加载
- 一. 加载 Loading
- 二. 连接 Linking
- 1. 验证 Verification
- 2. 准备 Preparation
- 3. 解析 Resolution
- 三. 初始化 Initialization
类加载是运行时环境的一个重要核心功能。
类加载的主要功能:把 .class 文件加载到内存中构建成为类对象。
什么时候会进行类加载呢 ?
不是说 java 程序一运行,就把所有的类都加载了,而是用到的时候再加载。(懒汉模式)
那什么时候算是用到了呢 ?
- 创建类的实例
- 调用类的静态方法 / 静态属性
- 加载子类,就会先加载父类
类的生命周期是这样的:
其中前 5 步是固定的顺序并且也是类加载的过程,其中 中间的 3 步都属于连接,所以对于类加载来
说总共分为以下几个步骤:
- 加载 Loading
- 连接 Linking:
验证 Verification
准备 Preparation
解析 Resolution - 初始化 Initialization
下面我们分别来看每个步骤的具体执行内容。
一. 加载 Loading
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流 (.class 文件)。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。虚拟机会将类文件的二进制字节流解析并转化为方法区
(Java 8之后称为元空间 Metaspace)中的运行时数据结构。这些数据结构包括类的字段、方法、父类、接口、常量池等信息。 - 在第2步之后,虚拟机会创建一个 java.lang.Class 对象,用于表示刚刚加载的类。这个java.lang.Class对象包含了对类的引用、方法区数据结构的指针等,它是在Java程序中访问类元信息的入口。这个对象的生成是为了让Java程序能够在运行时通过反射等手段访问和操作加载的类。
.class 文件就是把 .java 文件中核心信息都表达出来,只不过组织格式发生了变化。
Loading 会把从 .class 文件中解读到的信息初步填写到类对象中。
二. 连接 Linking
1. 验证 Verification
验证是连接阶段的第一步,这一阶段的目的是确保 Class文件的字节 流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。
验证选项:
- 文件格式验证
- 字节码验证
- 符号引用验证…
如果不符合规范类加载就会失败,抛出异常。
2. 准备 Preparation
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。
注意这里面设置的初始值指的是默认值
比如此时有这样一行代码:
public static int value = 123;
它是初始化 value 的 int 值为 0,而非 123。
3. 解析 Resolution
解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
注意:常量池中不是只有 字符串常量
运行时常量池: 存放字面量与符号引用。
- 字面量 : 字符串常量 (JDK 8 移动到堆中) 、final常量、基本数据类型的值。
- 符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。
举个栗子:
class Example {
public int count = 0;
public void increment() {
count++;
}
}
public class Main {
public static void main(String[] args) {
Example e = new Example();
int currentCount = e.count; // 引用Example类的count字段
e.increment(); // 调用Example类的increment方法
}
}
-
解析 e.count:在这个步骤中,虚拟机将符号引用 e.count 解析为实际的内存地址,以便能够访问count字段的值。
-
解析 e.increment():虚拟机将符号引用 e.increment() 解析为实际的方法内存地址,以便能够正确地调用increment方法。
到这里, 你可能有些疑惑 ?这个实例属性 count 不是在运行阶段创建对象时才分配空间 ?但是类加载阶段不是先于运行阶段么 ?
- 确实,Resolution 阶段的主要任务是将符号引用替换为实际的直接引用,但这些直接引用在Resolution 阶段只是一种占位符,它们在内存中并不代表实际的对象或实例属性。
- 直到对象实例化时,实例属性的内存空间才是真正有效的,并且可以存储实际的属性值。Resolution 阶段的替换只是为了确保在运行时可以正确地引用这些属性,但不会创建对象实例或分配实例属性的内存空间。
这个分离的设计有助于节省内存和提高性能,因为不是所有类都会被实例化,只有在需要时才会分配内存。
但是对于类名, 方法名, 静态属性的符号引用替换为直接引用是没有这个问题的。
三. 初始化 Initialization
执行类的静态初始化。在类首次被主动使用(例如,创建对象实例、访问静态字段或调用静态方法)时触发。
在这个阶段,虚拟机会初始化类中的静态字段和静态代码块,确保它们按照指定的顺序被正确地初始化。
此时上述的代码:
public static int value = 123;
此时 value 的值才是 123.