java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JVM类加载机制与双亲委派

JVM类加载机制与双亲委派使用及说明

作者:是码龙不是码农

文章介绍了JVM的四大类加载器(启动类加载器、扩展类加载器、应用程序类加载器和自定义类加载器),双亲委派机制及其核心优势,以及如何打破双亲委派机制的场景和实现方式

一、JVM 四大类加载器

JVM 的类加载器(ClassLoader)负责将.class字节码文件加载到内存中,并生成对应的Class对象。

Java 默认提供了四层层级结构的类加载器,从上到下依次为:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器。

1. 层级结构与核心特性

类加载器名称英文 / 简称加载范围实现方式父加载器
启动类加载器Bootstrap ClassLoaderJRE 核心类库:rt.jar、resources.jar、sun.boot.class.path路径下的类(如java.lang.*、java.util.*)C/C++ 实现,JVM 内置,无对应 Java 对象,无法在代码中直接获取无(顶层加载器)

扩展类加载器

(jdk9+后改名为平台类加载器)

Extension ClassLoaderJRE 扩展目录jre/lib/ext、java.ext.dirs指定路径下的扩展类Java 实现,继承自URLClassLoader启动类加载器
应用程序类加载器Application ClassLoader(系统类加载器)项目classpath下的自定义类、第三方依赖包Java 实现,继承自URLClassLoader,是ClassLoader.getSystemClassLoader()的返回值扩展类加载器
自定义类加载器Custom ClassLoader自定义路径的类(网络、加密字节码、自定义目录等)继承ClassLoader重写核心方法实现默认为应用程序类加载器

补充关键说明

2. 代码验证类加载器

通过简单代码查看不同类的加载器,直观理解层级关系:

public class ClassLoaderDemo {
    public static void main(String[] args) {
        // 1. 核心类:由Bootstrap加载,输出null
        ClassLoader bootstrapLoader = String.class.getClassLoader();
        System.out.println("String类加载器: " + bootstrapLoader);

        // 2. 系统类加载器(应用类加载器)
        ClassLoader appLoader = ClassLoaderDemo.class.getClassLoader();
        System.out.println("自定义类加载器: " + appLoader);

        // 3. 应用类加载器的父加载器:扩展类加载器
        ClassLoader extLoader = appLoader.getParent();
        System.out.println("应用类加载器的父加载器: " + extLoader);

        // 4. 扩展类加载器的父加载器:Bootstrap,输出null
        System.out.println("扩展类加载器的父加载器: " + extLoader.getParent());
    }
}

二、双亲委派机制

1. 定义

双亲委派机制是 JVM 类加载的默认规则:当一个类加载器收到类加载请求时,不会自己先尝试加载,而是将请求向上委托给父加载器,递归向上直到顶层启动类加载器;只有当父加载器无法加载该类时,子加载器才会尝试自己加载。

2. 执行流程(递归逻辑)

自定义类加载器收到加载请求,先检查是否已加载该类,已加载则直接返回Class对象;

未加载则委托给父加载器(应用类加载器)

应用类加载器重复检查逻辑,委托给扩展类加载器

扩展类加载器重复检查逻辑,委托给启动类加载器

启动类加载器尝试在自身加载路径查找类:

若所有层级加载器都无法加载,抛出ClassNotFoundException

3. 核心优势

避免类重复加载:保证同一个类在 JVM 中只被加载一次,生成唯一的Class对象;

保障 Java 核心 API 安全(沙箱安全):防止恶意代码自定义核心类(如java.lang.String)替换 JDK 原生类,避免核心 API 被篡改;

层级管理类资源:规范不同范围类的加载边界,提升类加载的稳定性。

4. 源码层面解析

双亲委派的核心逻辑封装在ClassLoaderloadClass(String name, boolean resolve)方法中(JDK8 源码核心逻辑):

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 {
                    // 3. 无父加载器,直接使用启动类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父加载器无法加载,捕获异常
            }
            // 4. 父加载器加载失败,当前加载器自行加载
            if (c == null) {
                c = findClass(name);
            }
        }
        // 解析类
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

关键方法:findClass()是子类的扩展点,默认抛出异常,自定义加载器需要重写它。

三、打破双亲委派机制

1. 为什么要打破?

默认的双亲委派无法满足所有场景,常见需求:

2. 打破的核心原理

双亲委派的逻辑写在loadClass()方法中,重写loadClass()方法,跳过向上委托的逻辑,就能直接打破该机制。

3. 标准实现方式:自定义类加载器

步骤

完整示例代码

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 打破双亲委派的自定义类加载器
 */
public class CustomClassLoader extends ClassLoader {
    // 自定义类加载路径
    private final String classPath;

    public CustomClassLoader(String classPath) {
        // 关键:不指定父加载器(默认父加载器为系统类加载器,也可手动设置)
        this.classPath = classPath;
    }

    /**
     * 重写loadClass,核心:跳过双亲委派的向上委托逻辑
     */
    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 1. 检查是否已加载
            Class<?> clazz = findLoadedClass(name);
            if (clazz != null) {
                return clazz;
            }

            // 2. 打破规则:核心类仍交给Bootstrap加载,避免JDK核心类加载异常
            if (name.startsWith("java.")) {
                return getSystemClassLoader().loadClass(name);
            }

            // 3. 不委托父加载器,直接自己加载
            try {
                clazz = findClass(name);
            } catch (Exception e) {
                // 加载失败,降级为系统类加载器加载
                return super.loadClass(name, resolve);
            }

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

    /**
     * 重写findClass:从自定义路径读取字节码
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassBytes(name);
        if (classData == null) {
            throw new ClassNotFoundException("无法找到类:" + name);
        }
        // 将字节码转换为Class对象
        return defineClass(name, classData, 0, classData.length);
    }

    /**
     * 从文件系统读取.class字节码
     */
    private byte[] getClassBytes(String className) {
        String path = classPath + "/" + className.replace(".", "/") + ".class";
        try (FileInputStream fis = new FileInputStream(path);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            return bos.toByteArray();
        } catch (IOException e) {
            return null;
        }
    }
}

测试代码

public class TestCustomLoader {
    public static void main(String[] args) throws Exception {
        // 自定义加载路径
        CustomClassLoader loader = new CustomClassLoader("D:/test/classes");
        // 加载自定义类
        Class<?> clazz = loader.loadClass("com.test.DemoClass");
        // 打印加载器,验证为自定义加载器
        System.out.println("类加载器: " + clazz.getClassLoader());
    }
}

4. 行业中打破双亲委派的经典场景

场景 1:Tomcat 等 Web 容器

场景 2:JDBC SPI 服务发现

场景 3:热部署 / 热加载

总结

核心知识点回顾:

  1. 四大类加载器:Bootstrap(顶层、C 实现)→ Extension → Application → 自定义加载器,层级为组合关系而非继承;
  2. 双亲委派机制:向上委托、向下加载,核心价值是防重复加载、保障核心 API 安全,逻辑在ClassLoader.loadClass()中;
  3. 打破方式重写loadClass()方法跳过向上委托,配合自定义findClass()实现个性化加载;
  4. 工业级场景:Tomcat 类隔离、JDBC SPI、热部署框架是打破双亲委派的典型应用。

面试答题话术参考:

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文