Skip to content

类的生命周期

一、类的生命周期概述

  • 阶段:加载(Loading)、连接(Linking,包括验证、准备、解析)、初始化(Initialization)、使用(Using)、卸载(Unloading)。
  • 顺序性
  • 加载、验证、准备、初始化顺序固定。
  • 解析可能在初始化后,支持运行时绑定(动态绑定)。
  • 交叉性:各阶段通常交叉进行,一个阶段可能激活另一阶段。

二、类加载阶段

  • 定义:查找并加载类的二进制数据。
  • 任务
  • 通过全限定名获取类的二进制字节流。
  • 将字节流转换为方法区的运行时数据结构。
  • 在堆中生成 java.lang.Class 对象,作为方法区数据的入口。
  • 特点
  • 可控性强,支持系统类加载器和自定义类加载器。
  • 不要求首次主动使用时加载,可预加载(若失败,首次使用时抛 LinkageError)。
  • 加载方式
  • 本地文件系统。
  • 网络下载 .class 文件。
  • ZIP/JAR 归档文件。
  • 数据库提取。
  • 动态编译 Java 源文件。

三、连接阶段

1. 验证(Verification)
  • 目的:确保字节流符合 JVM 规范且安全。
  • 步骤
  • 文件格式验证:检查魔数(0xCAFEBABE)、版本号、常量池等。
  • 元数据验证:语义分析(如是否有父类)。
  • 字节码验证:数据流和控制流分析,确保逻辑合法。
  • 符号引用验证:确保解析可执行。
  • 可选性:可通过 -Xverify:none 关闭,缩短加载时间。
2. 准备(Preparation)
  • 任务:为类变量(static)分配内存并赋默认值。
  • 特点
  • 仅针对类变量,实例变量在对象实例化时分配。
  • 默认值是数据类型的零值(如 int 为 0,Objectnull)。
  • 显式赋值(如 static int value = 3)在初始化阶段执行。
  • 特殊情况
  • static final 常量在准备阶段赋值为 ConstantValue 属性值(如 static final int value = 3)。
  • 未显式赋值的局部变量编译不通过。
3. 解析(Resolution)
  • 定义:将常量池的符号引用替换为直接引用。
  • 目标
  • 类/接口、字段、方法(类方法、接口方法)、方法类型、方法句柄、调用点限定符。
  • 符号引用:字面量描述目标。
  • 直接引用:指针、偏移量或句柄。

四、初始化阶段

  • 任务:为类变量赋正确初始值。
  • 方式
  • 声明时指定初始值。
  • 使用静态代码块(<clinit> 方法)。
  • 步骤
  • 若类未加载和连接,先完成加载和连接。
  • 若父类未初始化,先初始化父类。
  • 执行类的初始化语句。
  • 触发时机(主动使用):
  • 创建实例(new)。
  • 访问/赋值静态变量。
  • 调用静态方法。
  • 反射(Class.forName)。
  • 初始化子类(触发父类初始化)。
  • JVM 启动时的主类。

五、使用与卸载

  • 使用
  • 通过 Class 对象访问方法区数据。
  • 对象实例在堆中。
  • 卸载
  • 条件
    1. 执行 System.exit()
    2. 程序正常结束。
    3. 异常终止。
    4. 操作系统错误导致 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/extjava.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 生效修改。
  • 双亲委派机制
  • 过程
    1. AppClassLoader 委托 ExtClassLoader
    2. ExtClassLoader 委托 BootstrapClassLoader
    3. 上层失败后逐级尝试加载。
  • 代码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 + 自定义。
  • 机制:双亲委派确保安全与一致性,自定义加载器扩展灵活性。