全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-11 7 分钟 ✍️ juanwangdev

Java类加载机制与双亲委派模型

类加载机制将.class文件加载到JVM内存,是Java运行的基础。

类加载过程

Java
┌────────────┐
│  加载      │ ← 查找并加载字节码
└────────────┘
      ↓
┌────────────┐
│  链接      │
├────────────┤
│  验证      │ ← 验证字节码合法性
│  准备      │ ← 分配内存,初始化静态变量
│  解析      │ ← 符号引用转为直接引用
└────────────┘
      ↓
┌────────────┐
│  初始化    │ ← 执行类初始化代码
└────────────┘

详细步骤

1. 加载

  • 通过类名获取二进制字节流
  • 转换为方法区的运行时数据结构
  • 在堆中生成Class对象

2. 验证

  • 文件格式验证:魔数0xCAFEBABE
  • 元数据验证:语义分析
  • 字节码验证:数据流分析
  • 符号引用验证:引用是否存在

3. 准备

Java
public class Example {
    private static int value = 100;  // 准备阶段:value=0
    private static final int MAX = 100; // 准备阶段:MAX=100
}

静态变量在准备阶段初始化为零值,final变量直接赋值

4. 解析

将符号引用转为直接引用:

Java
符号引用:字符串形式的引用
  "java/lang/Object"

直接引用:内存地址
  0x12345678

5. 初始化

执行类初始化代码<clinit>方法:

Java
public class Example {
    private static int value = 100;        // <clinit>执行赋值
    private static int count;

    static {
        count = 10;  // <clinit>执行静态块
    }
}

类加载器层次

Java
┌─────────────────────────────────────┐
│        Bootstrap ClassLoader        │ ← JVM内置,加载核心类
│       (rt.jar, java.*包)            │
└─────────────────────────────────────┘
                ↓ parent
┌─────────────────────────────────────┐
│       Extension ClassLoader         │ ← 加载扩展类
│       (ext目录下的jar)              │
└─────────────────────────────────────┘
                ↓ parent
┌─────────────────────────────────────┐
│       Application ClassLoader       │ ← 加载应用类
│       (classpath下的类)             │
└─────────────────────────────────────┘
                ↓ parent
┌─────────────────────────────────────┐
│       Custom ClassLoader            │ ← 自定义加载器
└─────────────────────────────────────┘

双亲委派模型

工作流程

Java
收到加载请求
      ↓
委派给父加载器
      ↓
父加载器无法加载 → 尔回子加载器
      ↓
子加载器尝试加载

实现代码

Java
protected Class<?> loadClass(String name, boolean resolve) {
    // 1. 检查是否已加载
    Class<?> c = findLoadedClass(name);

    if (c == null) {
        // 2. 委派给父加载器
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            c = findBootstrapClassOrNull(name);
        }

        // 3. 父加载器无法加载,自己尝试
        if (c == null) {
            c = findClass(name);
        }
    }

    if (resolve) {
        resolveClass(c);
    }
    return c;
}

双亲委派优点

  1. 安全性:防止核心类被篡改
Java
// 自定义java.lang.String会失败
// Bootstrap已加载,不会用自定义的
  1. 避免重复加载:父加载器已加载的类,子加载器不会再加载

打破双亲委派

方式1:自定义加载器覆写loadClass

text
public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) {
        // 直接加载,不委派父加载器
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            c = findClass(name);  // 自己加载
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

方式2:线程上下文加载器

text
// JDBC驱动加载示例
Thread.currentThread().setContextClassLoader(driverLoader);

// SPI框架使用上下文加载器加载实现类
// 让父加载器加载接口,子加载器加载实现

方式3:OSGi热部署

每个Bundle有独立类加载器,打破双亲委派实现模块隔离。

自定义类加载器

text
public class MyClassLoader extends ClassLoader {
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);
        return defineClass(name, data, 0, data.length);
    }

    private byte[] loadClassData(String name) {
        String path = classPath + name.replace('.', '/') + ".class";
        try {
            InputStream in = new FileInputStream(path);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            return out.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

// 使用
MyClassLoader loader = new MyClassLoader("/custom/path/");
Class<?> clazz = loader.loadClass("com.example.CustomClass");

类加载时机

触发条件说明
new对象创建实例
访问静态字段getstatic/putstatic
调用静态方法invokestatic
反射Class.forName()
初始化子类父类先初始化
主类包含main方法的类

被动引用不触发初始化

text
public class Parent {
    static { System.out.println("Parent初始化"); }
    public static int value = 100;
}

// 子类引用父类静态字段,不初始化子类
public class Child extends Parent {
    static { System.out.println("Child初始化"); }
}

Child.value;  // 只初始化Parent,不初始化Child

注意事项

双亲委派保证核心类安全,不应轻易打破

自定义加载器建议覆写findClass而非loadClass

线程上下文加载器用于SPI场景

类初始化是懒加载,不会加载所有类

要点总结

  1. 类加载过程:加载 → 验证 → 准备 → 解析 → 初始化
  2. 类加载器层次:Bootstrap → Extension → Application → Custom
  3. 双亲委派:先委派父加载器,保证安全性和唯一性
  4. 打破双亲委派:覆写loadClass、上下文加载器、OSGi
  5. 自定义加载器覆写findClass方法

📝 发现内容有误?点击此处直接编辑

← 上一篇 Java对象创建与内存分配
下一篇 → CountDownLatch、CyclicBarrier、Semaphore
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库