类的生命周期
一、类的生命周期概述
- 阶段:加载(Loading)、连接(Linking,包括验证、准备、解析)、初始化(Initialization)、使用(Using)、卸载(Unloading)。
- 顺序性:
- 加载、验证、准备、初始化顺序固定。
- 解析可能在初始化后,支持运行时绑定(动态绑定)。
- 交叉性:各阶段通常交叉进行,一个阶段可能激活另一阶段。
二、类加载阶段
- 定义:查找并加载类的二进制数据。
- 任务:
- 通过全限定名获取类的二进制字节流。
- 将字节流转换为方法区的运行时数据结构。
- 在堆中生成
java.lang.Class
对象,作为方法区数据的入口。
- 特点:
- 可控性强,支持系统类加载器和自定义类加载器。
- 不要求首次主动使用时加载,可预加载(若失败,首次使用时抛
LinkageError
)。
- 加载方式:
- 本地文件系统。
- 网络下载
.class
文件。
- ZIP/JAR 归档文件。
- 数据库提取。
- 动态编译 Java 源文件。
三、连接阶段
1. 验证(Verification)
- 目的:确保字节流符合 JVM 规范且安全。
- 步骤:
- 文件格式验证:检查魔数(
0xCAFEBABE
)、版本号、常量池等。
- 元数据验证:语义分析(如是否有父类)。
- 字节码验证:数据流和控制流分析,确保逻辑合法。
- 符号引用验证:确保解析可执行。
- 可选性:可通过
-Xverify:none
关闭,缩短加载时间。
2. 准备(Preparation)
- 任务:为类变量(
static
)分配内存并赋默认值。
- 特点:
- 仅针对类变量,实例变量在对象实例化时分配。
- 默认值是数据类型的零值(如
int
为 0,Object
为 null
)。
- 显式赋值(如
static int value = 3
)在初始化阶段执行。
- 特殊情况:
static final
常量在准备阶段赋值为 ConstantValue
属性值(如 static final int value = 3
)。
- 未显式赋值的局部变量编译不通过。
3. 解析(Resolution)
- 定义:将常量池的符号引用替换为直接引用。
- 目标:
- 类/接口、字段、方法(类方法、接口方法)、方法类型、方法句柄、调用点限定符。
- 符号引用:字面量描述目标。
- 直接引用:指针、偏移量或句柄。
四、初始化阶段
- 任务:为类变量赋正确初始值。
- 方式:
- 声明时指定初始值。
- 使用静态代码块(
<clinit>
方法)。
- 步骤:
- 若类未加载和连接,先完成加载和连接。
- 若父类未初始化,先初始化父类。
- 执行类的初始化语句。
- 触发时机(主动使用):
- 创建实例(
new
)。
- 访问/赋值静态变量。
- 调用静态方法。
- 反射(
Class.forName
)。
- 初始化子类(触发父类初始化)。
- JVM 启动时的主类。
五、使用与卸载
- 使用:
- 通过
Class
对象访问方法区数据。
- 对象实例在堆中。
- 卸载:
- 条件:
- 执行
System.exit()
。
- 程序正常结束。
- 异常终止。
- 操作系统错误导致 JVM 终止。
- 过程:类不再被引用且 GC 回收。
六、类加载器与 JVM 类加载机制
1. 类加载器层次
- JVM 视角:
- 启动类加载器(Bootstrap ClassLoader):C++ 实现,加载核心类库(
rt.jar
)。
- 其他类加载器:Java 实现,继承
ClassLoader
,由启动类加载器加载。
- 开发视角:
- Bootstrap ClassLoader:加载
$JAVA_HOME/jre/lib
或 -Xbootclasspath
指定路径。
- Extension ClassLoader:加载
$JAVA_HOME/jre/lib/ext
或 java.ext.dirs
。
- Application ClassLoader:加载用户类路径(
ClassPath
)。
- 自定义类加载器:继承
ClassLoader
,实现特定加载逻辑。
- 关系:通过组合(非继承)实现父子关系。
2. 类加载器示例
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader); // AppClassLoader
System.out.println(loader.getParent()); // ExtClassLoader
System.out.println(loader.getParent().getParent()); // null (Bootstrap)
}
}
- 输出:
sun.misc.Launcher$AppClassLoader@64fef26a
sun.misc.Launcher$ExtClassLoader@1ddd40f3
null
- 解析:
Bootstrap
用 C++ 实现,无法直接引用,返回 null
。
3. 类加载方式
- 三种方式:
- 命令行启动时由 JVM 加载。
Class.forName()
动态加载。
ClassLoader.loadClass()
动态加载。
- 示例:
public class LoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = HelloWorld.class.getClassLoader();
System.out.println(loader);
loader.loadClass("Test2"); // 不执行静态块
// Class.forName("Test2"); // 执行静态块
// Class.forName("Test2", false, loader); // 不执行静态块
}
}
class Test2 {
static {
System.out.println("静态初始化块执行了!");
}
}
- 区别:
Class.forName()
:加载并初始化(执行静态块)。
ClassLoader.loadClass()
:仅加载,不初始化。
Class.forName(name, initialize, loader)
:可控制是否初始化。
4. JVM 类加载机制
- 全盘负责:加载类时负责其依赖类的加载。
- 父类委托:优先委托父加载器加载。
- 缓存机制:已加载类缓存,重启 JVM 生效修改。
- 双亲委派机制:
- 过程:
AppClassLoader
委托 ExtClassLoader
。
ExtClassLoader
委托 BootstrapClassLoader
。
- 上层失败后逐级尝试加载。
- 代码:
java
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
c = findClass(name);
}
}
if (resolve) resolveClass(c);
return c;
}
- 优势:
5. 自定义类加载器
public class MyClassLoader extends ClassLoader {
private String root;
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) throw new ClassNotFoundException();
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData(String className) {
String fileName = root + File.separator + className.replace('.', File.separatorChar) + ".class";
try (InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) throws Exception {
MyClassLoader loader = new MyClassLoader();
loader.setRoot("D:\\temp");
Class<?> testClass = loader.loadClass("com.pdai.jvm.classloader.Test2");
Object obj = testClass.newInstance();
System.out.println(obj.getClass().getClassLoader()); // MyClassLoader
}
}
- 注意:
- 类名需全限定名。
- 避免重写
loadClass()
,保留双亲委托。
- 避免与系统类路径冲突。
七、总结
- 生命周期:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载。
- 类加载器:Bootstrap、Extension、Application + 自定义。
- 机制:双亲委派确保安全与一致性,自定义加载器扩展灵活性。