Skip to content
清晨的一缕阳光
返回

Java 类加载机制详解

Java 类加载机制详解

类加载是 JVM 将.class 文件加载到内存并验证、解析、初始化的过程,理解类加载机制对框架开发和故障排查至关重要。

一、类加载过程

flowchart LR
    A[加载] --> B[验证]
    B --> C[准备]
    C --> D[解析]
    D --> E[初始化]

1.1 加载(Loading)

三件事

  1. 通过全限定类名获取二进制字节流
  2. 将静态存储结构转化为方法区的运行时数据结构
  3. 在堆中生成 Class 对象作为访问入口

来源

1.2 验证(Verification)

确保字节流符合 JVM 规范:

验证阶段
├── 文件格式验证(魔数、版本号)
├── 元数据验证(语义检查)
├── 字节码验证(数据流、控制流)
└── 符号引用验证(解析前检查)

1.3 准备(Preparation)

为类变量分配内存并设置默认值:

public static int value = 123;
// 准备阶段:value = 0(int 默认值)
// 初始化阶段:value = 123

1.4 解析(Resolution)

将常量池中的符号引用替换为直接引用:

符号引用 → 直接引用

Lcom/example/MyClass; → 0x12345678(内存地址)

1.5 初始化(Initialization)

执行类构造器<clinit>()方法:

static {
    // 静态代码块
    value = 123;
}

二、类加载器

2.1 加载器层次

graph TB
    A[Bootstrap ClassLoader]
    B[Extension ClassLoader]
    C[Application ClassLoader]
    D[Custom ClassLoader]
    
    A -->|parent| B
    B -->|parent| C
    C -->|parent| D

2.2 启动类加载器(Bootstrap)

实现:C/C++(rt.jar 中)
职责:加载核心类库(java.lang.*, java.util.*等)
路径:$JAVA_HOME/lib

2.3 扩展类加载器(Extension)

实现:sun.misc.Launcher$ExtClassLoader
职责:加载扩展类库
路径:$JAVA_HOME/lib/ext

2.4 应用程序类加载器(Application)

实现:sun.misc.Launcher$AppClassLoader
职责:加载 classpath 中的类
路径:-classpath 或 java.class.path

2.5 查看加载器

// 获取当前类的加载器
ClassLoader loader = MyClass.class.getClassLoader();
System.out.println(loader);
// AppClassLoader

// 获取系统类加载器
ClassLoader system = ClassLoader.getSystemClassLoader();
System.out.println(system);
// AppClassLoader

// 获取平台类加载器(JDK 9+)
ClassLoader platform = ClassLoader.getPlatformClassLoader();

三、双亲委派模型

3.1 工作原理

收到类加载请求

先委托给父加载器

父加载器无法加载

自己尝试加载

3.2 源码分析

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否已加载
        Class<?> c = findLoadedClass(name);
        
        if (c == null) {
            try {
                // 2. 委托父加载器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父加载器无法加载
            }
            
            if (c == null) {
                // 3. 自己加载
                c = findClass(name);
            }
        }
        
        if (resolve) {
            resolveClass(c);
        }
        
        return c;
    }
}

3.3 优点

  1. 安全性:防止核心 API 被篡改

    // 自定义 java.lang.String 不会被加载
    // Bootstrap 加载器优先加载 rt.jar 中的 String
  2. 避免重复加载:父加载器已加载,子类无需再加载

  3. 保证类的唯一性:同一个类在 JVM 中只有一个 Class 对象


四、破坏双亲委派

4.1 SPI 机制

// JDBC Driver 加载
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 父加载器无法加载厂商实现的 Driver
// 需要线程上下文类加载器(ThreadContextClassLoader)

4.2 热部署

应用服务器(Tomcat)
├── 每个 Web 应用独立 ClassLoader
├── 优先加载应用类
└── 实现热部署、热替换

4.3 代码热替换

// OSGi 模块化框架
// 每个 Bundle 独立 ClassLoader
// 支持模块动态安装、卸载、更新

4.4 自定义加载器

public class MyClassLoader extends ClassLoader {
    private String classPath;
    
    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadClassData(name);
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name);
        }
    }
    
    private byte[] loadClassData(String className) throws IOException {
        String path = classPath + "/" + className.replace('.', '/') + ".class";
        try (InputStream is = new FileInputStream(path)) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            return baos.toByteArray();
        }
    }
}

五、类加载器实战

5.1 判断类是否相同

// 两个类相同的条件:
// 1. 类名相同
// 2. 类加载器相同

ClassLoader loader1 = new MyClassLoader("/path1");
ClassLoader loader2 = new MyClassLoader("/path2");

Class<?> c1 = loader1.loadClass("com.example.MyClass");
Class<?> c2 = loader2.loadClass("com.example.MyClass");

System.out.println(c1 == c2); // false(加载器不同)

5.2 内存泄漏

// OSGi 场景:Bundle 卸载后 ClassLoader 无法回收
// 原因:静态变量持有 Class 引用

// 解决:清理静态引用,确保 ClassLoader 可回收

5.3 Tomcat 类加载器

Tomcat 类加载层次
├── Bootstrap(JVM 启动)
├── System(系统类库)
├── Common(Tomcat 公共类)
├── Catalina(容器类)
└── WebApp(每个应用独立)
    ├── WebAppClassLoader
    └── 优先加载应用类(反双亲委派)

六、常见问题

6.1 ClassCastException

// 不同 ClassLoader 加载的同一类转换时报错
Object obj = loader1.loadClass("MyClass").newInstance();
MyClass mc = (MyClass) obj; // ClassCastException

// 解决:确保使用同一 ClassLoader

6.2 ClassNotFoundException

// 类不存在或加载器无法找到
try {
    Class.forName("com.example.MyClass");
} catch (ClassNotFoundException e) {
    // 检查 classpath 或加载器配置
}

6.3 NoClassDefFoundError

// 编译时有,运行时找不到
// 原因:类初始化失败或依赖缺失

七、总结

类加载核心要点:

阶段作用关键操作
加载获取字节流文件系统/JAR/网络
验证安全性检查格式/元数据/字节码
准备分配内存设置默认值
解析符号→直接常量池替换
初始化执行静态代码<clinit>()
加载器职责路径
Bootstrap核心类库$JAVA_HOME/lib
Extension扩展类库$JAVA_HOME/lib/ext
Application应用类classpath

双亲委派保证安全性,特殊场景(SPI、热部署)需要破坏委派模型。


分享这篇文章到:

上一篇文章
ConcurrentHashMap 原理详解
下一篇文章
Java 垃圾回收机制详解