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

五、总结(核心要点)

  1. 双亲委派核心逻辑 :子加载器先委托父加载器加载,父加载器无法加载时,子加载器通过 findClass() 自定义加载,核心是「父优先、防篡改」;
  2. Java 17 关键调整:移除扩展类加载器,替换为平台类加载器,强化模块系统融合,推荐自定义加载器注册并行加载能力;
  3. 安全核心 :任何尝试覆盖 java.* 核心包的行为,都会被编译期 / 运行期拦截,抛出 SecurityException,这是双亲委派机制的核心价值;
  4. 实践要点 :自定义类加载器需重写 findClass()(而非 loadClass()),避免破坏双亲委派规则,保证类加载的规范性。
相关推荐
AC赳赳老秦5 小时前
OpenClaw 与 FineBI 联动方案:公开数据自动采集与实时业务分析看板实践
大数据·开发语言·python·php·finebi·deepseek·openclaw
通信瓦工5 小时前
OCP 冷却环境与冷却剂分配单元(CDU)子项目
java·开发语言
坤坤子吖5 小时前
Python基础语法学习:函数
开发语言·笔记·python·学习
言乐66 小时前
Python体现逻辑回归和决策树的区别
开发语言·python·算法·决策树·逻辑回归
LOVE️YOU6 小时前
Python 类与对象的核心逻辑:从 self、cls 到点号、括号与链式调用
开发语言·python
顶点多余6 小时前
C/C++常见面试题
开发语言·c++
(Charon)6 小时前
【C++面试】右值引用与移动语义:std::move到底做了什么
开发语言·c++
TheITSea6 小时前
JavaScript Web APIs
开发语言·前端·javascript
空心木偶☜6 小时前
A2A2A(多智能体协作)
开发语言·python·ai·ai编程
Cc.Y6 小时前
Java零基础入门:类与对象深度掌握 —— 从“零散变量“到“打包管理“
java·开发语言·python