文章目录
- 类的生命周期
- 类的加载:查找并加载类的二进制数据
- 验证
- 准备
- 解析
- 初始化
- 类加载器
- 启动类加载器(Bootstrap ClassLoader)
- 扩展类加载器(Extension ClassLoader)
- 应用程序类加载器(Application ClassLoader)
- 自定义类加载器(User-Defined ClassLoader)
- 双亲委派模型
- 工作原理
- 优点
- 打破双亲委派
- 打破双亲委派
类的生命周期
类加载的过程包括了加载
、验证
、准备
、解析
、初始化
五个阶段。在这五个阶段中,
加载
、验证
、准备
和初始化
这四个阶段发生的顺序是确定的,而解析
阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
类的加载:查找并加载类的二进制数据
- 任务:将类的字节码文件(
.class
文件)加载到内存中。
-
具体步骤:
- 通过类的全限定名(包名 + 类名)查找字节码文件。
- 将字节码文件读取到内存中。
- 在内存中生成一个代表该类的
java.lang.Class
对象。
-
加载.class文件的方式
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
- 将Java源文件动态编译为.class文件
验证
- 任务:确保加载的字节码是合法的,符合 JVM 规范。
- 验证内容:
- 文件格式验证:
- 检查字节码文件是否符合 JVM 规范(如魔数、版本号等)。
- 元数据验证:
- 检查类的元数据是否合法(如是否有父类、是否继承了 final 类等)。
- 字节码验证:
- 检查方法体中的字节码指令是否合法,是否会危害 JVM。
- 符号引用验证:
- 检查类、方法、字段的引用是否存在且可访问。
- 文件格式验证:
准备
-
任务:为类的静态变量分配内存,并设置默认初始值。
-
具体行为:
- 静态变量会被分配内存并初始化为零值(如
int
初始化为0
,boolean
初始化为false
)。 - 如果是常量(
static final
),则直接赋值为指定的值。
- 静态变量会被分配内存并初始化为零值(如
-
示例:
java">static int a = 10; // 准备阶段:a = 0
static final int b = 20; // 准备阶段:b = 20
解析
- 任务:将常量池中的符号引用转换为直接引用。
- 符号引用:一组符号描述引用目标(如类名、方法名)。
- 直接引用:指向目标在内存中的指针或偏移量。
- 解析内容:
- 类或接口的解析。
- 字段的解析。
- 方法的解析。
初始化
- 任务:执行类的静态初始化代码(静态代码块和静态变量赋值)。
- 触发条件:
- 创建类的实例(
new
)。 - 访问类的静态变量或静态方法。
- 使用反射加载类(如
Class.forName()
)。 - 初始化子类时,父类会先被初始化。
- 创建类的实例(
java">static int a = 10; // 初始化阶段:a = 10
static {System.out.println("Static block executed");
}
类加载器
JVM 通过类加载器来加载类。Java 中有以下三类类加载器:
启动类加载器(Bootstrap ClassLoader)
扩展类加载器(Extension ClassLoader)
- 职责:加载扩展类库(如
javax.*
等)。 - 实现:由
sun.misc.Launcher$ExtClassLoader
实现。 - 路径:加载
JAVA_HOME/lib/ext
目录下的类。
应用程序类加载器(Application ClassLoader)
- 职责:加载用户类路径(ClassPath)下的类。
- 实现:由
sun.misc.Launcher$AppClassLoader
实现。 - 路径:加载
-classpath
或-cp
指定的类。
自定义类加载器(User-Defined ClassLoader)
- 职责:用户可以通过继承
ClassLoader
类实现自定义类加载器。 - 用途:
- 热部署:在不重启 JVM 的情况下重新加载类。
- 模块化加载:加载特定模块的类。
- 加密类加载:加载加密的字节码文件。
双亲委派模型
双亲委派模型是类加载器之间的协作机制。
工作原理
- 当一个类加载器需要加载类时,它首先会委托给父类加载器加载。
- 如果父类加载器无法加载,子类加载器才会尝试加载。
- 最终,如果所有父类加载器都无法加载,才会抛出
ClassNotFoundException
。
优点
- 安全性:防止用户自定义的类替换核心类(如
java.lang.String
)。 - 避免重复加载:确保类只被加载一次。
打破双亲委派
- 在某些场景下(如 SPI 服务加载、模块化加载),可能需要打破双亲委派模型。
- 安全性:防止用户自定义的类替换核心类(如
java.lang.String
)。 - 避免重复加载:确保类只被加载一次。
打破双亲委派
- 在某些场景下(如 SPI 服务加载、模块化加载),可能需要打破双亲委派模型。
- 例如:
Thread.currentThread().setContextClassLoader()
可以设置线程上下文类加载器。