类加载过程是 Java 虚拟机 (JVM) 将 Java 代码编译后的字节码文件加载到内存中,并进行解析和验证,最终使程序能够运行的关键步骤。
- 类加载过程:加载->连接->初始化。
- 连接过程又可分为三步:验证->准备->解析。
1. 加载(Loading)
将字节码文件读入内存,并将其转换为 JVM 能够识别的 Class 对象。
这个过程通常涉及以下几个步骤:
- 查找:JVM根据类的全名(包括包名)查找相应的字节码文件(.class文件)。这可以从本地文件系统、网络或其他资源中获取。
- 读取:找到字节码文件后,JVM会将其读取到内存中。
- 解析:将字节码中的符号引用(如类名、方法名、字段名等)转换为直接引用。这一过程通常在后续步骤中进行。
2.验证 (Verification)
检查字节码文件的合法性和安全性,确保其符合 JVM 规范,防止恶意代码攻击。
- 文件格式验证:检查字节码文件的格式是否正确。
- 字节码验证:确保字节码符合Java语言的语法和语义规则,例如,确保方法的调用和字段的访问是合法的。
- 类型验证:检查类的继承关系和类型兼容性,确保类型安全。
3. 准备(Preparation)
在准备阶段,JVM会为类的静态变量分配内存,并将其初始化为默认值(如0、null等)。此时,类的静态变量会被分配到方法区(Method Area)。
4. 解析(Resolution)
解析(Resolution)是将符号引用转换为直接引用的过程。
主要是将字节码中的符号引用(如类名、方法名和字段名)转换为在内存中实际指向这些元素的地址。这一过程确保了在运行时能够正确地访问和调用类、方法和字段。
符号引用和直接引用
- 符号引用:
- 符号引用是指在字节码中以字符串形式表示的类、方法或字段的名称。
- 例如,类B的符号引用可能是字符串"B",而方法display的符号引用可能是字符串"display"。
- 直接引用:
- 直接引用是指在内存中实际的地址或指针,指向类、方法或字段的具体实现。直接引用允许JVM快速访问这些元素,而无需再次查找。
示例
class A {
void methodB() {
B b = new B(); // 这里需要解析B类
b.display(); // 这里需要解析display方法
}
}
class B {
void display() {
System.out.println("Hello from B");
}
}
- 当
methodB
被调用时,JVM首先会解析类B,找到它在内存中的地址。 - 然后,在调用
b.display()
时,JVM会解析display
方法,找到该方法在内存中的地址。
5. 初始化(Initialization)
初始化阶段是类加载过程的最后一步。在这一阶段,JVM会执行类的初始化代码,包括静态变量的初始化和静态代码块的执行。初始化过程遵循以下规则:
- 静态变量初始化:根据在类中定义的顺序,初始化所有静态变量。
- 静态代码块执行:执行类中的所有静态代码块。
6. 使用(Using)
类加载完成后,JVM可以使用该类进行实例化、方法调用等操作。此时,类的生命周期开始,JVM可以根据需要创建类的实例并调用其方法。
7. 卸载(Unloading)
在某些情况下,当类不再被使用时,JVM会将其从内存中卸载。卸载过程通常由垃圾回收机制管理,以释放内存资源。
卸载类需要满足 3 个要求:
- 该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
- 该类没有在其他任何地方被引用
- 该类的类加载器的实例已被 GC
所以,在 JVM 生命周期内,由 jvm 自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。
参考:类加载过程详解 | JavaGuide