类加载过程
类加载过程
类加载过程为 7 个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中,验证、准备和解析这三个阶段可以统称为连接(Linking)。
1. 加载(Loading)
- 加载类的字节码: 类加载器读取类文件(通常是
.class
文件)的字节码内容,并将其转换为方法区中的类对象。这个过程中,类的静态变量、方法、以及其他信息都被加载到内存中。 - 查找和加载: 加载器会按照一定的顺序查找类文件,包括从本地文件系统、JAR包、甚至网络等不同位置加载类。
2. 验证(Verification)
- 确保字节码的正确性和安全性: 验证阶段主要是为了确保被加载的类符合JVM的要求,防止恶意代码或错误代码破坏系统的安全和稳定。验证包括四个方面:
- 文件格式验证: 检查字节流是否符合Class文件格式的规范。
- 元数据验证: 检查类的元数据信息,如类、接口、字段、方法的类型和修饰符是否合法。
- 字节码验证: 确保程序代码不会做出危害虚拟机安全的操作,如非法数据类型转换。
- 符号引用验证: 确保类、字段、方法等符号引用是可用的且符合访问控制规则。
3. 准备(Preparation)
- 分配内存: 为类的静态变量分配内存,并初始化为默认值(例如数值类型为0,引用类型为null)。
- 常量池中的符号引用转化为直接引用: 在这个阶段,类的常量池中记录的符号引用将会被替换成直接引用。
4. 解析(Resolution)
- 解析符号引用为直接引用: 解析阶段是将常量池中的符号引用替换为直接引用的过程,如将一个方法调用符号引用解析为实际的方法对象。解析包括类或接口的解析、字段解析、方法解析等。
- 动态链接: 在这个过程中,可以通过lazy resolution(懒解析)机制,即在真正用到某个符号时才进行解析。
5. 初始化(Initialization)
- 初始化静态变量: 执行类构造器
<clinit>()
方法。<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。 - 静态代码块的执行: 类中定义的静态初始化块也会在这个阶段被执行。静态初始化块和静态变量的初始化按照它们在类中出现的顺序依次执行。
类卸载过程
在Java虚拟机中,类的卸载是指将已经加载的类从内存中移除的过程。类卸载的主要目的是释放内存资源,通常与类加载器的生命周期密切相关。
类卸载的条件
- 没有任何实例: 类的所有实例对象都已被垃圾回收,即堆中不存在该类的任何实例。
- 类的Class对象不可达: 类的Class对象本身没有被任何引用持有,这意味着没有任何代码或数据在使用该类。
- 类加载器可以被回收: 加载该类的类加载器本身没有被任何引用持有,且没有其他类依赖于它。类加载器的实例没有被使用时,也会被垃圾回收。
满足这些条件后,JVM会认为该类和它的类加载器可以被卸载。
类卸载的过程
- 垃圾回收标记: 在垃圾回收过程中,JVM首先会标记所有不再使用的对象,包括类的实例、Class对象和类加载器。
- 判断是否满足卸载条件: 如果一个类满足上述条件,并且它的类加载器也满足卸载条件,那么JVM将会执行类卸载操作。
- 清理类加载器和类的元数据: JVM会清除该类加载器的内部数据结构,包括类的定义、常量池、方法表等。这个过程释放了用于类和类加载器的内存。
- 释放类相关资源: 卸载类时,JVM还会释放与该类相关的资源,包括静态变量、静态方法等占用的内存。这也意味着类的静态代码块和静态变量不会再次初始化。
- 垃圾回收: 最终,类加载器和类相关的所有对象(包括Class对象)都会被垃圾回收,彻底释放内存。
双亲委派机制
双亲委派机制(Parent Delegation Model)是Java类加载器的一种设计模式,它确保Java类加载的一致性和安全性。通过这种机制,Java虚拟机可以避免重复加载类,确保同一个类在整个应用中唯一加载一次。以下是双亲委派机制的详细解释:
双亲委派机制的工作原理
- 类加载请求: 当应用程序请求加载某个类时,类加载器会首先将这个请求委派给其父类加载器。
- 向上委派: 父类加载器接收到请求后,会继续将这个请求委派给它的父类加载器。这一过程递归地向上进行,直到到达最顶层的类加载器。
- 顶层加载器: 顶层的类加载器通常是Bootstrap ClassLoader,它负责加载Java核心类库(例如
java.lang.*
等)。 - 加载类: 顶层类加载器在其负责的范围内查找该类,如果找到,则加载该类并返回;如果没有找到,则将请求下放给子加载器。
- 向下加载: 如果某个层次的类加载器无法找到类,它将把加载请求返回给最初请求的类加载器,让它尝试自己加载该类。
- 自定义类加载器: 如果所有的父类加载器都未能加载该类,那么最初请求的类加载器会尝试自行加载,通常这是一个自定义的类加载器,用于加载应用程序的类或者特定的第三方库。
双亲委派机制的优点
- 类加载 的安全性: 双亲委派机制可以防止核心Java类库被篡改或伪造。例如,如果没有双亲委派机制,应用程序可以定义一个名为
java.lang.String
的类,这样就有可能伪造核心库的功能或行为。 - 避免重复加载: 同一个类在Java虚拟机中只加载一次。通过委派机制,系统类和应用类在不同类加载器之间不会重复加载,节省内存资源。
- 统一性: 确保了Java核心库的类在各个部分一致性。无论哪个部分请求加载核心库中的类,最终都由最上层的Bootstrap ClassLoader加载,确保版本和实现的一致性。
例外情况
虽然双亲委派机制是Java类加载器的一般规则,但也存在一些例外情况:
- 自定义类加载器: 应用程序可以创建自己的类加载器,不一定需要严格遵循双亲委派机制。自定义类加载器可以选择先加载某些特定的类,然后再委派给父加载器。
- 打破双亲委派: 有时为了实现某些特殊的需求(如在Web应用服务器中隔离不同应用的类库),可能需要打破双亲委派机制。这通常通过特殊设计的类加载器实现。
- 引导类加载器(Bootstrap ClassLoader): 它是由JVM自身实现的,负责加载JRE核心库,无法被Java程序直接引用或获取。