07_JVM 双亲委派机制

一、双亲委派机制核心概述

双亲委派机制(Parent Delegation Model)是 Java 类加载器的核心协作规则,也是 JVM 保障类加载安全、有序的核心设计。其核心思想可通俗理解为:「类加载请求先 "向上找爸爸"(委托父加载器),爸爸搞不定了,儿子再自己动手」------ 即子加载器收到类加载请求时,优先委托父加载器加载,仅当父加载器无法完成加载时,子加载器才自行加载。

该机制在 Java 17 中保留核心逻辑,但对类加载器层级做了调整(移除扩展类加载器,替换为平台类加载器),同时强化了与模块系统(JPMS)的融合。

二、双亲委派机制工作原理

1. Java 17 类加载器层级(核心调整点)

Java 17 简化并规范了类加载器层级,相比 Java 8 移除了「扩展类加载器(Extension ClassLoader)」,核心层级如下:

tex 复制代码
启动类加载器(Bootstrap ClassLoader)→ 
平台类加载器(Platform ClassLoader)→ 
应用类加载器(App ClassLoader)→ 
自定义类加载器

各加载器的核心职责:

加载器类型 所属层面 加载范围 Java 17 特性说明
启动类加载器(Bootstrap) JVM 底层(C++) java.base 等核心模块(如 rt.jar、jrt-fs.jar 中的核心类,如 java.lang.String 无对应的 Java 对象,getClassLoader() 返回 null
平台类加载器(Platform) JDK 内置(Java) JDK 平台模块(如 java.sqljava.xml 等非核心模块) 替代 Java 8 的扩展类加载器,管控平台级 API
应用类加载器(App) JDK 内置(Java) 应用程序的 classpath 路径下的所有类(用户编写的业务类) 也叫系统类加载器(System ClassLoader)
自定义类加载器 用户自定义 自定义路径下的类(如热部署、类隔离场景) 需重写 findClass(),推荐注册并行加载能力

2. 双亲委派完整工作流程

以「自定义类加载器请求加载 com.dwl.Demo」为例,流程如下:

  1. 请求委托:自定义类加载器收到加载请求,不立即处理,先委托给父加载器(应用类加载器);

  2. 递归向上:应用类加载器继续委托给父加载器(平台类加载器),平台类加载器再委托给顶层的启动类加载器;

  3. 自上而下尝试加载

    • 启动类加载器检查自身加载范围:若能加载(如核心类),直接返回 Class 对象;若不能,通知子加载器(平台类加载器);
    • 平台类加载器检查自身加载范围:能加载则返回,不能则通知子加载器(应用类加载器);
    • 应用类加载器检查 classpath:能加载则返回,不能则通知子加载器(自定义类加载器);
    • 自定义类加载器检查自定义路径:能加载则返回,不能则抛出 ClassNotFoundException

3. 工作流程可视化

  1. 委托加载请求 2. 委托加载请求 3. 委托加载请求 4. 检查是否可加载
    如 rt.jar 核心类 能
    不能
    检查平台模块/ext 目录

    不能
    检查 classpath 路径

    不能
    检查自定义路径

    不能
    自定义类加载器

Custom ClassLoader
应用类加载器

App ClassLoader
平台类加载器

Platform ClassLoader
启动类加载器

Bootstrap ClassLoader
能否加载?
返回已加载的 Class 对象
通知子加载器尝试加载
能否加载?
通知子加载器尝试加载
能否加载?
通知子加载器尝试加载
能否加载?
抛出 ClassNotFoundException

4. 核心原则

  • 单向委托:仅子委托父,父不委托子(反向委托需特殊机制,如 SPI 服务加载);
  • 父优先加载 :核心类(如 java.lang.String)必须由顶层加载器加载,防止篡改;
  • 类唯一性:同一类仅被「首次成功加载的加载器」缓存,避免重复加载(不同加载器加载同一类视为不同类)。

三、代码演示(Java 17 可直接运行)

所有示例均基于 Java 17 编写,需确保:

① JDK 17 环境;

② 代码路径与包名一致;

③ 自定义路径需提前创建并放入编译后的 .class 文件。

演示 1:核心类加载流程(验证双亲委派优先级)

功能 :查看 java.lang.String 和自定义类的加载器,验证层级关系。

java 复制代码
package com.dwl.ex01_类加载子系统;


/**
 * @Description
 * @Version 1.0.0
 * @Date 2026/2/9 2:09
 * @Author Dwl
 */
public class CoreClassLoadingDemo {
    public static void main(String[] args) {
        // 1. 获取核心类(String)的加载器:启动类加载器无Java对象,返回null
        ClassLoader stringClassLoader = String.class.getClassLoader();
        System.out.println("1. java.lang.String 的加载器: " + stringClassLoader);
        System.out.println("   说明:null 代表启动类加载器(Bootstrap ClassLoader)\n");

        // 2. 获取当前类的加载器:应用类加载器
        ClassLoader appClassLoader = CoreClassLoadingDemo.class.getClassLoader();
        System.out.println("2. 当前类(CoreClassLoadingDemo)的加载器: " + appClassLoader);
        System.out.println("   说明:Java 17 中显示为 jdk.internal.loader.ClassLoaders$AppClassLoader\n");

        // 3. 打印完整的类加载器层级
        System.out.println("3. 类加载器层级(双亲委派链):");
        printClassLoaderHierarchy(appClassLoader);
    }

    /**
     * 递归打印类加载器层级(从当前加载器到顶层)
     *
     * @param loader 起始类加载器
     */
    private static void printClassLoaderHierarchy(ClassLoader loader) {
        int level = 0;
        while (loader != null) {
            System.out.println("   " + "→ ".repeat(level + 1) + loader);
            loader = loader.getParent();
            level++;
        }
        // 启动类加载器无Java对象,单独标注
        System.out.println("   " + "→ ".repeat(level + 1) + "启动类加载器(Bootstrap ClassLoader,C++实现)");
    }
}

运行结果

关键说明 :启动类加载器由 C++ 实现,不属于 Java 对象,因此 getClassLoader() 返回 null,这是 Java 17 与其他版本一致的特性。

演示 2:禁止自定义核心包类(安全保护)

功能 :验证双亲委派机制阻止自定义 java.lang 包下的类,防止核心 API 被篡改。

步骤 1:尝试定义 java.lang.String(编译期报错)
java 复制代码
package java.lang;

/**
 * @Description javac -encoding UTF-8 --patch-module java.base=F:\idea-workspase\JVM-Explorer\JDK17 "-XDignore.symbol.file"
 *    F:\idea-workspase\JVM-Explorer\JDK17\java\lang\String.java
 * @Version 1.0.0
 * @Date 2026/2/9 2:22
 * @Author Dwl
 */
public class String {
    static {
        System.out.println("自定义 java.lang.String 静态代码块");
    }

    // 核心修正:显式指定参数为原生 java.lang.String[]
    public static void main(java.lang.String[] args) {
        System.out.println("自定义 String 类运行成功!");
    }
}

编译错误信息

步骤 2:尝试通过自定义加载器加载(运行期拦截)
java 复制代码
package com.dwl.ex01_类加载子系统;


import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * @Description 类加载安全拦截演示
 * 演示如何触发 JVM 对 java.lang 包的安全保护机制(SecurityException)
 * 原理说明:
 * 1. JVM 默认通过「双亲委派模型」保护 java.lang 核心包,自定义类加载器默认无法加载该包下的类
 * 2. 重写 loadClass 方法破坏双亲委派后,自定义类加载器会尝试加载 java.lang.String,
 * 此时 JVM 在 defineClass 阶段会检测到非法的 java.lang 包名,抛出安全异常
 * 注意:该代码仅用于学习演示,生产环境绝对禁止破坏双亲委派/操作 java.lang 包
 * @Version 1.0.0
 * @Date 2026/2/9 2:21
 * @Author Dwl
 */
public class ForbiddenClassLoadDemo {

    /**
     * 主方法:执行自定义类加载器加载 java.lang.String 的测试
     *
     * @param args 命令行参数(未使用)
     * @throws Exception 捕获类加载过程中的所有异常(简化演示)
     */
    public static void main(String[] args) throws Exception {
        // 1. 创建自定义类加载器,指定自定义 java.lang.String.class 文件的存放根路径
        // 路径说明:该路径下需存在 "java/lang/String.class" 自定义类文件
        CustomClassLoader loader = new CustomClassLoader("F:/idea-workspase/JVM-Explorer/JDK17/java/lang");

        try {
            // 2. 尝试加载自定义的 java.lang.String 类
            // 注:默认 loadClass 会触发双亲委派,重写后会跳过父加载器直接加载
            Class<?> forbiddenClass = loader.loadClass("java.lang.String");

            // 3. 若加载成功(实际不会走到这一步),打印加载的类名
            System.out.println("加载的类: " + forbiddenClass.getName());
        } catch (Exception e) {
            System.err.println("加载失败(安全拦截): " + e.getMessage());
        }
    }

    /**
     * 自定义类加载器(核心逻辑)
     * 作用:重写 loadClass 破坏双亲委派,强制加载自定义路径下的 java.lang.String
     * 父类:继承自 JDK 核心类 ClassLoader
     */
    static class CustomClassLoader extends ClassLoader {
        // 自定义类文件的根路径(存放编译后的 .class 文件)
        private final String classPath;

        /**
         * 自定义类加载器构造方法
         *
         * @param classPath 自定义 .class 文件的根路径
         */
        public CustomClassLoader(String classPath) {
            // 显式指定父加载器为「应用类加载器(AppClassLoader)」
            // 注:ClassLoader 默认父加载器就是 AppClassLoader,此处显式指定是为了清晰
            super(ClassLoader.getSystemClassLoader());
            this.classPath = classPath;
        }

        /**
         * 重写 loadClass 方法,破坏「双亲委派模型」
         * 双亲委派默认逻辑:先委托父加载器加载 → 父加载器加载失败才自己加载
         * 重写后逻辑:跳过父加载器,直接调用自身的 findClass 方法加载
         *
         * @param name    要加载的类全限定名(如 java.lang.String)
         * @param resolve 是否解析类(解析:验证/准备/解析类的符号引用,此处按需开启)
         * @return 加载后的 Class 对象
         * @throws ClassNotFoundException 类加载失败时抛出
         */
        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            // 类加载锁:保证类加载的线程安全(JVM 规范要求)
            synchronized (getClassLoadingLock(name)) {
                // 第一步:检查该类是否已被当前类加载器加载过(缓存检查)
                Class<?> c = findLoadedClass(name);

                // 第二步:若未加载过,跳过父加载器,直接调用自身 findClass 加载(破坏双亲委派的核心)
                if (c == null) {
                    // 核心:不调用 super.loadClass,跳过父加载器
                    c = findClass(name);
                }

                // 第三步:若需要解析类,则执行解析操作(可选,演示场景下不影响核心逻辑)
                if (resolve) {
                    resolveClass(c);
                }

                // 返回加载后的 Class 对象
                return c;
            }
        }

        /**
         * 重写 findClass 方法:实现自定义的类加载逻辑
         * 作用:从指定路径读取 .class 文件字节数组,调用 defineClass 定义类
         * 注:defineClass 是触发 JVM 安全拦截的核心方法
         *
         * @param name 要加载的类全限定名
         * @return 加载后的 Class 对象
         * @throws ClassNotFoundException 读取文件/定义类失败时抛出
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 打印日志:验证该方法是否被执行(破坏双亲委派后才会执行)
            System.out.println("自定义类加载器的 findClass 执行了!目标类:" + name);

            try {
                // 1. 拼接类文件路径:将类全限定名的 "." 替换为 "/",拼接 .class 后缀
                // 例如:java.lang.String → java/lang/String.class
                String className = name.replace('.', '/') + ".class";
                // 2. 读取自定义路径下的 .class 文件为字节数组
                byte[] classBytes = Files.readAllBytes(Paths.get(classPath, className));

                // 3. 调用父类的 defineClass 方法:将字节数组转换为 Class 对象
                // 核心触发点:JVM 会检查类的包名,若为 java.lang 则抛出 SecurityException
                return defineClass(name, classBytes, 0, classBytes.length);
            } catch (Exception e) {
                throw new ClassNotFoundException("类加载失败:" + name, e);
            }
        }
    }
}

运行错误信息(Java 17 安全拦截):

核心结论 :Java 17 从「编译期 + 运行期」双层拦截自定义 java.* 核心包类,彻底杜绝核心 API 被篡改。

演示 3:自定义加载器加载普通类(正常流程)

功能:演示自定义加载器遵循双亲委派机制,加载非核心类的完整流程。

步骤 1:待加载的普通类
java 复制代码
package com.dwl;

/**
 * 普通业务类:用于演示自定义加载器加载
 */
public class Demo {
    // 静态代码块:类加载时执行,用于验证加载时机
    static {
        System.out.println("[Demo 类] 已被加载(静态代码块执行)");
    }

    public void hello() {
        System.out.println("[Demo 类] Hello from Demo!");
    }
}
步骤 2:带日志追踪的自定义加载器
java 复制代码
package com.dwl.ex01_类加载子系统;


import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * @Description 演示:自定义类加载器遵循双亲委派机制加载普通类
 * @Version 1.0.0
 * @Date 2026/2/9 17:41
 * @Author Dwl
 */
public class CustomClassLoaderDemo {
    public static void main(String[] args) throws Exception {
        // 前置条件:将 Demo.class 文件放入以下自定义路径(需替换为自己的路径)
        String customClassPath = "F:\\idea-workspase\\JVM-Explorer\\JDK17\\java";

        // 1. 创建自定义类加载器
        CustomClassLoader customLoader = new CustomClassLoader(customClassPath);

        // 2. 打印加载器层级信息
        System.out.println("===== 加载器层级信息 =====");
        System.out.println("自定义加载器: " + customLoader);
        System.out.println("自定义加载器的父加载器(AppClassLoader): " + customLoader.getParent());
        System.out.println("AppClassLoader 的父加载器(PlatformClassLoader): " + customLoader.getParent().getParent());
        System.out.println("PlatformClassLoader 的父加载器(Bootstrap,null): " + customLoader.getParent().getParent().getParent());
        System.out.println("========================\n");

        // 3. 触发类加载(遵循双亲委派:先父后子)
        Class<?> demoClass = customLoader.loadClass("com.dwl.Demo");

        // 4. 验证加载结果
        System.out.println("\n===== 最终加载结果 =====");
        System.out.println("Demo 类的实际加载器: " + demoClass.getClassLoader());
    }

    /**
     * 自定义类加载器
     * 核心:重写 loadClass 保持双亲委派,重写 findClass 实现自定义加载逻辑
     */
    static class CustomClassLoader extends ClassLoader {
        private final String classPath;

        /**
         * 构造器:指定父加载器 + 注册并行加载能力
         *
         * @param classPath 自定义类文件的根路径
         */
        public CustomClassLoader(String classPath) {
            super(ClassLoader.getSystemClassLoader()); // 父加载器为应用类加载器
            this.classPath = classPath;
            registerAsParallelCapable(); // 注册并行加载能力,提升多线程加载性能
        }

        /**
         * 重写 loadClass:严格遵循双亲委派逻辑,增加日志追踪流程
         */
        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            // 加锁:保证类加载的线程安全(JVM 类加载器规范要求)
            synchronized (getClassLoadingLock(name)) {
                System.out.printf("[自定义加载器] 处理类加载请求:%s%n", name);

                // 第一步:检查类是否已缓存(避免重复加载)
                Class<?> loadedClass = findLoadedClass(name);
                if (loadedClass != null) {
                    System.out.printf("[自定义加载器] 类已缓存,直接返回:%s%n", name);
                    if (resolve) {
                        resolveClass(loadedClass);
                    }
                    return loadedClass;
                }

                try {
                    // 第二步:委托父加载器加载(双亲委派核心:父优先)
                    System.out.printf("[自定义加载器] 委托父加载器(%s)加载:%s%n",
                            getParent().getClass().getSimpleName(), name);
                    loadedClass = super.loadClass(name, false);
                    System.out.printf("[自定义加载器] 父加载器加载成功:%s%n", name);
                } catch (ClassNotFoundException e) {
                    // 第三步:父加载器加载失败,自定义加载器自行加载
                    System.out.printf("[自定义加载器] 父加载器加载失败,尝试自定义加载:%s%n", name);
                    loadedClass = findClass(name);
                    System.out.printf("[自定义加载器] 自定义加载成功:%s%n", name);
                }

                // 解析类(链接阶段,可选)
                if (resolve) {
                    resolveClass(loadedClass);
                }
                return loadedClass;
            }
        }

        /**
         * 自定义加载核心逻辑:从指定路径读取 .class 文件并定义类
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                // 拼接类文件路径:包名转路径 + .class 后缀
                String classFilePath = classPath + "/" + name.replace('.', '/') + ".class";
                System.out.printf("[自定义加载器] 读取类文件:%s%n", classFilePath);

                // 读取字节码
                byte[] classBytes = Files.readAllBytes(Paths.get(classFilePath));

                // 定义类:将字节码转换为 Class 对象(ClassLoader 核心 API)
                return defineClass(name, classBytes, 0, classBytes.length);
            } catch (IOException e) {
                throw new ClassNotFoundException("自定义加载器读取类文件失败:" + name, e);
            }
        }
    }
}

运行结果

关键说明

  • classpath 中存在该类,父加载器(AppClassLoader)会优先加载,自定义加载器不会执行 findClass
  • classpath 中不存在,自定义加载器才会从指定路径加载,符合「父优先」的双亲委派规则。

四、Java 17 中双亲委派的核心优势

  1. 核心 API 安全防护 :彻底阻止自定义 java.* 包类,避免核心类(如 StringObject)被恶意篡改,是 JVM 安全沙箱的基础;
  2. 避免类重复加载:父加载器加载过的类,子加载器直接复用缓存,减少内存占用,保证类的唯一性;
  3. 适配模块系统(JPMS):启动类加载器加载核心模块、平台加载器加载平台模块、应用加载器加载用户模块,实现模块隔离;
  4. 并行加载优化 :Java 17 支持 registerAsParallelCapable() 注册并行加载能力,提升多线程场景下的类加载效率。

五、总结(核心要点)

  1. 双亲委派核心逻辑 :子加载器先委托父加载器加载,父加载器无法加载时,子加载器通过 findClass() 自定义加载,核心是「父优先、防篡改」;
  2. Java 17 关键调整:移除扩展类加载器,替换为平台类加载器,强化模块系统融合,推荐自定义加载器注册并行加载能力;
  3. 安全核心 :任何尝试覆盖 java.* 核心包的行为,都会被编译期 / 运行期拦截,抛出 SecurityException,这是双亲委派机制的核心价值;
  4. 实践要点 :自定义类加载器需重写 findClass()(而非 loadClass()),避免破坏双亲委派规则,保证类加载的规范性。
相关推荐
前端程序猿i2 小时前
第 8 篇:Markdown 渲染引擎 —— 从流式解析到安全输出
开发语言·前端·javascript·vue.js·安全
kronos.荒2 小时前
滑动窗口:寻找字符串中的字母异位词
开发语言·python
_codemonster2 小时前
java web修改了文件和新建了文件需要注意的问题
java·开发语言·前端
甄心爱学习2 小时前
【python】list的底层实现
开发语言·python
独自破碎E2 小时前
BISHI41 【模板】整除分块
java·开发语言
hewence12 小时前
Kotlin CoroutineContext 详解
android·开发语言·kotlin
IvanCodes2 小时前
七、C语言指针
c语言·开发语言
edisao3 小时前
第三章 合规的自愿
jvm·数据仓库·python·神经网络·决策树·编辑器·动态规划
寻寻觅觅☆3 小时前
东华OJ-基础题-120-顺序的分数(C++)
开发语言·c++·算法