类加载器 & 双亲委派机制

什么是类加载器(ClassLoader)?

类加载器(ClassLoader) 是 Java 虚拟机(JVM)加载字节码文件(.class)到内存,并转换成 Class 对象的组件。

1、 为什么需要类加载器?

Java 语言的特点是动态加载:

  • 代码编译后变成 .class 文件,但 .class 文件本身不能直接运行,必须由 类加载器 加载到 JVM 内存中,才能被使用。
  • 类加载器让 Java 可以在运行时动态加载类,比如:
    • 反射(Reflection)
    • 热部署(Hot Deployment)
    • 插件机制(Plugin System)

2、 类加载器的作用

类加载器的主要功能是:

  1. 加载类(Load Class):从磁盘、网络、Jar 包等地方加载 .class 文件到 JVM 内存。
  2. 连接类(Link Class):
    (1) 验证(Verify):检查字节码是否符合 JVM 规范,防止恶意代码执行。
    (2) 准备(Prepare):为类的静态变量分配内存,并初始化默认值。
    (3) 解析(Resolve):将符号引用转换为直接引用(如方法、字段)。
  3. 初始化类(Initialize Class):
    执行类的 静态代码块(static {}) 和 静态变量初始化。

3、Java 的类加载器层次结构

Java 提供了 层次化的类加载器(资源隔离,避免重复加载),主要有以下几种:

类加载器作用典型加载的类
Bootstrap ClassLoader(引导类加载器)加载 Java 核心类java.lang.String, java.util.List
ExtClassLoader(扩展类加载器)/ PlatformClassLoader(Java 9+)加载 lib/ext/ 目录下的类javax.crypto.Cipher
AppClassLoader(应用类加载器)加载 classpath 下的类com.example.Main
自定义 ClassLoader(用户自定义)加载自定义路径的类插件、热部署类

💡 层次关系(双亲委派模型 Parent Delegation Model)

Bootstrap ClassLoader (引导类加载器)       
             ↑
ExtClassLoader / PlatformClassLoader(扩展类/平台类加载器)       
             ↑
AppClassLoader(应用类加载器)       
             ↑
自定义 ClassLoader(用户自定义的类加载器)

4、Java 如何使用类加载器?
(1)获取类加载器

public class ClassLoaderTest {
    public static void main(String[] args) {
        // 获取当前类的类加载器(AppClassLoader)
        ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println("AppClassLoader: " + appClassLoader);

        // 获取当前类加载器的父类加载器(ExtClassLoader / PlatformClassLoader)
        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("ExtClassLoader: " + extClassLoader);

        // 获取 ExtClassLoader 的父类加载器(BootstrapClassLoader,C++ 实现,返回 null)
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println("BootstrapClassLoader: " + bootstrapClassLoader);
    }
}

示例输出(Java 8):

AppClassLoader: sun.misc.Launcher$AppClassLoader@<hash>
ExtClassLoader: sun.misc.Launcher$ExtClassLoader@<hash>
BootstrapClassLoader: null

(2)使用 Class.forName() 反射加载类

Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println("类加载器:" + clazz.getClassLoader()); // 输出 null(由 Bootstrap 加载)

(3)使用 ClassLoader.loadClass() 手动加载类

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> myClass = classLoader.loadClass("com.example.MyClass");
System.out.println("MyClass 加载器:" + myClass.getClassLoader());

5、自定义类加载器(扩展 ClassLoader)
有时,我们需要自定义类加载器,比如:

  • 从网络或数据库加载类
  • 动态代理
  • 热部署(Hot Deployment)

示例:自定义 ClassLoader

import java.io.*;

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 fileName = classPath + name.replace(".", "/") + ".class";
        try (InputStream is = new FileInputStream(fileName);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            return bos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException("加载类失败", e);
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("/path/to/classes/");
        Class<?> clazz = myClassLoader.loadClass("com.example.HelloWorld");
        System.out.println("自定义类加载器加载的类:" + clazz.getName());
    }
}

示例解析:

  • 继承 ClassLoader 并重写 findClass() 方法。
  • 从指定路径加载 .class 文件的二进制数据,转换为 Class 对象。
  • 通过 defineClass() 方法将字节码转换为 Java 类。

什么是双亲委派机制(Parent Delegation Model)?

双亲委派机制是 Java 类加载器的一种工作方式。它规定:

  • 当某个类加载器加载一个类时,它会先请求它的 父类加载器 尝试加载。
  • 如果父类加载器找不到这个类,它才会自己尝试加载。
    目的:
  • 防止核心类库被篡改(如 java.lang.String 不能被用户定义的类替换)。
  • 提高加载效率,避免重复加载同一个类。

1、双亲委派的类加载流程

假设要加载 com.example.MyClass,流程如下:

  1. AppClassLoader 先检查自己是否已加载过 com.example.MyClass。
  2. 如果没有,它先委托父加载器 ExtClassLoader 进行加载。
  3. ExtClassLoader 再委托BootstrapClassLoader 尝试加载。
  4. BootstrapClassLoader 只加载核心类库(如 java.lang.*),找不到 com.example.MyClass,返回 null。
  5. ExtClassLoader 也找不到 com.example.MyClass,返回 null。
  6. AppClassLoader 发现父类加载器都无法加载,自己来加载 com.example.MyClass。

2、验证双亲委派机制

public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("AppClassLoader: " + appClassLoader);

        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("ExtClassLoader: " + extClassLoader);

        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println("BootstrapClassLoader: " + bootstrapClassLoader); // null

        // 验证 java 核心类由 BootstrapClassLoader 加载
        System.out.println("String 类加载器:" + String.class.getClassLoader()); // null

        // 验证 AppClassLoader 加载自定义类
        System.out.println("当前类的类加载器:" + ClassLoaderTest.class.getClassLoader());
    }
}

示例输出(Java 8):

AppClassLoader: sun.misc.Launcher$AppClassLoader@<hash>
ExtClassLoader: sun.misc.Launcher$ExtClassLoader@<hash>
BootstrapClassLoader: null
String 类加载器:null
当前类的类加载器:sun.misc.Launcher$AppClassLoader@<hash>
  • String 由 BootstrapClassLoader 加载(返回 null)。
  • 自定义类由 AppClassLoader 加载。

3、三个类加载器之间的关系

不是继承关系,而是层级关系(父子关系)

  • 父类加载器不一定是 Java 类的父类,而是通过 parent 引用(组合的方式) 来建立层级关系。如下为ClassLoader中父加载器的定义:
public abstract class ClassLoader {
     // The parent class loader for delegation
     private final ClassLoader parent;
}
  • 例如:AppClassLoader 的 父类加载器 是 ExtClassLoader,但 AppClassLoader 和 ExtClassLoader 并没有继承关系,只是它的父加载器。

ClassLoader的继承体系

java.lang.Object
  └── java.lang.ClassLoader
        ├── java.net.URLClassLoader  (Java 8 及之前)
        │       ├── sun.misc.Launcher$AppClassLoader
        │       ├── sun.misc.Launcher$ExtClassLoader
        ├── jdk.internal.loader.BuiltinClassLoader  (Java 9+)
                ├── jdk.internal.loader.ClassLoaders$AppClassLoader
                ├── jdk.internal.loader.ClassLoaders$PlatformClassLoader
  • AppClassLoader 和 ExtClassLoader 继承自 URLClassLoader(Java 8)。
  • PlatformClassLoader 和 AppClassLoader 继承自 BuiltinClassLoader(Java 9)。
  • BootstrapClassLoader 不是 Java 类,没有继承 ClassLoader。

4、什么时候需要打破双亲委派?

通常情况下,不建议破坏双亲委派,除非有特殊需求:

  • Tomcat / OSGi 需要自定义类加载器以实现热加载和模块化。
  • Spring Boot 在 spring-boot-devtools 里使用了自定义类加载器,支持类热加载。
  • JDBC 驱动 需要自定义 Thread Context ClassLoader,否则 BootstrapClassLoader 无法加载第三方数据库驱动

5、如何破坏双亲委派机制?

双亲委派过程都是在loadClass方法中实现的,那么想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。
loadClass()、findClass()、defineClass()区别
ClassLoader中和类加载有关的方法有很多,前面提到了loadClass,除此之外,还有findClass和defineClass等,那么这几个方法有什么区别呢?

  • loadClass() 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中。
  • findClass() 根据名称或位置加载.class字节码
  • definclass() 把字节码转化为Class

示例:

import java.io.*;

public class MyClassLoader extends ClassLoader {
    private String classPath;

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

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 破坏双亲委派机制:先尝试自己加载
        if (name.startsWith("java.") || name.startsWith("javax.")) {
            return super.loadClass(name); // 保持 JDK 核心类不被篡改
        }

        try {
            byte[] data = loadClassData(name);
            Class<?> clazz = defineClass(name, data, 0, data.length);
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        } catch (IOException e) {
            return super.loadClass(name); // 加载失败时,交给父类加载器
        }
    }

    private byte[] loadClassData(String name) throws IOException {
        String fileName = classPath + name.replace(".", "/") + ".class";
        try (InputStream is = new FileInputStream(fileName);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            return bos.toByteArray();
        }
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("/path/to/classes/");
        Class<?> clazz = myClassLoader.loadClass("com.example.HelloWorld");
        System.out.println("自定义类加载器加载的类:" + clazz.getName());
        System.out.println("类的类加载器:" + clazz.getClassLoader());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值