京东Java面试被问:双亲委派模型被破坏的场景和原理

一、双亲委派模型回顾

1. 标准双亲委派流程

java

复制

下载

复制代码
// ClassLoader.loadClass() 方法核心逻辑
protected Class<?> loadClass(String name, boolean resolve) {
    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) {
                // 父类加载器加载失败
            }
            
            // 3. 父类加载器失败,自己尝试加载
            if (c == null) {
                c = findClass(name);
            }
        }
        return c;
    }
}

2. 类加载器层次结构

text

复制

下载

复制代码
Bootstrap ClassLoader (启动类加载器)
        ↑
Extension ClassLoader (扩展类加载器)
        ↑
Application ClassLoader (应用类加载器)
        ↑
Custom ClassLoader (自定义类加载器)

二、破坏双亲委派的主要场景

1. SPI机制(Service Provider Interface)

1.1 JDBC SPI破坏原理

java

复制

下载

复制代码
// JDBC DriverManager的类加载机制
public class DriverManager {
    static {
        // 初始化时加载所有Driver
        loadInitialDrivers();
    }
    
    private static void loadInitialDrivers() {
        // 使用ServiceLoader加载驱动
        ServiceLoader<Driver> loadedDrivers = 
            ServiceLoader.load(Driver.class);
        
        // 这里的Driver.class是由启动类加载器加载的
        // 但Driver实现类需要由应用类加载器加载
        // 破坏了双亲委派:父加载器请求子加载器加载类
    }
}

// ServiceLoader.load()方法
public static <S> ServiceLoader<S> load(Class<S> service) {
    // 关键:使用线程上下文类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return new ServiceLoader<>(service, cl);
}
1.2 上下文类加载器(ContextClassLoader)机制

java

复制

下载

复制代码
// 线程上下文类加载器的设置和获取
public class Thread implements Runnable {
    private ClassLoader contextClassLoader;
    
    public void setContextClassLoader(ClassLoader cl) {
        // 可以在运行时动态设置
        this.contextClassLoader = cl;
    }
    
    public ClassLoader getContextClassLoader() {
        return contextClassLoader;
    }
}

// JDBC使用示例
Connection conn = DriverManager.getConnection(url, user, password);
// 内部过程:
// 1. DriverManager由启动类加载器加载
// 2. 但mysql-connector-java.jar由应用类加载器加载
// 3. 通过Thread.currentThread().getContextClassLoader()打破双亲委派

2. Tomcat的类加载器设计

2.1 Tomcat类加载器架构

text

复制

下载

复制代码
      Bootstrap
         ↑
      System
         ↑
      Common
      ↗     ↖
 Webapp1   Webapp2  ← 每个Web应用独立类加载器
    ↑         ↑
 Jasper   Jasper

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

2.2 Web应用隔离的实现

java

复制

下载

复制代码
// Tomcat的WebappClassLoader
public class WebappClassLoader extends URLClassLoader {
    
    @Override
    public Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        synchronized (getClassLoadingLock(name)) {
            // 1. 检查本地缓存
            Class<?> clazz = findLoadedClass0(name);
            if (clazz != null) return clazz;
            
            // 2. 检查JVM缓存
            clazz = findLoadedClass(name);
            if (clazz != null) return clazz;
            
            // 3. 屏蔽J2SE和J2EE的核心类(不向上委托)
            if (isJ2SEClass(name) || isJ2EEClass(name)) {
                try {
                    clazz = getSystemClassLoader().loadClass(name);
                    if (clazz != null) return clazz;
                } catch (ClassNotFoundException e) {
                    // 继续
                }
            }
            
            // 4. 检查委托列表(某些包委托给父加载器)
            if (isDelegatedPackage(name)) {
                try {
                    clazz = getParent().loadClass(name);
                    if (clazz != null) return clazz;
                } catch (ClassNotFoundException e) {
                    // 继续
                }
            }
            
            // 5. 先在自己的Web应用内查找(打破双亲委派)
            try {
                clazz = findClass(name);
                if (clazz != null) return clazz;
            } catch (ClassNotFoundException e) {
                // 继续
            }
            
            // 6. 最后委托给父类加载器
            return super.loadClass(name, resolve);
        }
    }
    
    // 判断是否J2SE类
    private boolean isJ2SEClass(String name) {
        return name.startsWith("java.") || 
               name.startsWith("javax.") ||
               name.startsWith("sun.");
    }
}
2.3 Tomcat打破双亲委派的原因

java

复制

下载

复制代码
// 场景1:不同Web应用使用不同版本的库
// WebApp1使用Spring 4.x,WebApp2使用Spring 5.x
// 如果不打破双亲委派,两者会共用同一个Spring类

// 场景2:热部署支持
// Tomcat需要在不重启容器的前提下重新加载Web应用
public class HotDeployClassLoader extends URLClassLoader {
    // 每个版本使用新的ClassLoader实例
    // 旧版本的ClassLoader可以被GC回收
}

// 场景3:JSP编译支持
// Jasper编译器需要动态生成Servlet类
public class JasperClassLoader extends URLClassLoader {
    // JSP编译的类需要在Web应用类加载器中加载
    // 而不是共享的Common ClassLoader
}

3. OSGi(Open Service Gateway Initiative)

3.1 OSGi的类加载模型

text

复制

下载

复制代码
Bundle A ClassLoader
      ↗          ↖
Import Package  Export Package
      ↑            ↓
Bundle B ClassLoader
3.2 OSGi的平级类加载器委托

java

复制

下载

复制代码
// OSGi的BundleClassLoader实现
public class BundleClassLoader extends ClassLoader {
    
    // OSGi不使用标准的双亲委派
    // 而是使用图状依赖关系的类加载
    
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        // 1. 查找本地Bundle
        Class<?> clazz = findLocalClass(name);
        if (clazz != null) return clazz;
        
        // 2. 查找依赖的Bundle(平级查找,不是父级)
        clazz = findDependentBundleClass(name);
        if (clazz != null) return clazz;
        
        // 3. 查找Fragment Bundle
        clazz = findFragmentClass(name);
        if (clazz != null) return clazz;
        
        // 4. 查找DynamicImport
        clazz = findDynamicImportClass(name);
        if (clazz != null) return clazz;
        
        // 5. 最后才委托给父类加载器
        return super.loadClass(name, resolve);
    }
    
    private Class<?> findDependentBundleClass(String name) {
        // 根据Import-Package声明查找依赖的Bundle
        // 这是平级类加载器之间的委托,不是父子关系
        for (Bundle dependency : getDependencies()) {
            try {
                return dependency.loadClass(name);
            } catch (ClassNotFoundException e) {
                // 继续查找
            }
        }
        return null;
    }
}
3.3 OSGi打破双亲委派的原因

java

复制

下载

复制代码
// 原因1:模块化隔离
// 每个Bundle是独立的模块,有自己的类加载器
// 不同Bundle可以使用不同版本的库

// 原因2:动态性支持
// Bundle可以动态安装、更新、卸载
// 需要独立的类加载空间

// 原因3:服务注册与发现
// OSGi的服务模型需要类加载器隔离
public class ServiceTracker {
    // 服务实现可以在运行时动态替换
    // 需要类加载器隔离来支持热插拔
}

4. 热部署与动态代码更新

4.1 JRebel热部署实现

java

复制

下载

复制代码
// JRebel的类重加载机制
public class JRebelClassLoader extends ClassLoader {
    
    private Map<String, byte[]> reloadedClasses = new ConcurrentHashMap<>();
    
    @Override
    public Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        // 1. 检查是否是重加载的类
        byte[] reloadedBytes = reloadedClasses.get(name);
        if (reloadedBytes != null) {
            // 重新定义类,打破双亲委派
            return defineClass(name, reloadedBytes, 0, reloadedBytes.length);
        }
        
        // 2. 标准双亲委派
        return super.loadClass(name, resolve);
    }
    
    public void reloadClass(String name, byte[] newBytes) {
        // 存储新的类字节码
        reloadedClasses.put(name, newBytes);
        
        // 需要打破双亲委派,让新的类定义生效
        // 否则会加载到旧的缓存类
    }
}
4.2 Spring Boot DevTools热重启

java

复制

下载

复制代码
// 重启类加载器实现
public class RestartClassLoader extends URLClassLoader {
    
    // 使用两个类加载器实现重启
    // baseClassLoader: 加载不变的库(如第三方jar)
    // restartClassLoader: 加载应用程序类
    
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        // 1. 排除的包走父类加载器
        if (isExcludedPackage(name)) {
            return getParent().loadClass(name);
        }
        
        // 2. 应用类自己加载(打破双亲委派)
        try {
            Class<?> clazz = findClass(name);
            if (clazz != null) return clazz;
        } catch (ClassNotFoundException e) {
            // 继续
        }
        
        // 3. 库类委托给baseClassLoader
        return getBaseClassLoader().loadClass(name);
    }
}

三、实现自定义类加载器打破双亲委派

1. 自定义类加载器实现

java

复制

下载

复制代码
public class CustomClassLoader extends ClassLoader {
    
    // 类文件路径
    private String classPath;
    
    public CustomClassLoader(String classPath) {
        // 关键:不传递父加载器,或传递null
        // 这样就不会委托给父加载器
        super(null); // 打破双亲委派的关键
        this.classPath = classPath;
    }
    
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        synchronized (getClassLoadingLock(name)) {
            // 1. 先检查是否已加载
            Class<?> c = findLoadedClass(name);
            if (c != null) {
                return c;
            }
            
            // 2. 完全跳过父类加载器,自己直接加载
            // 这是打破双亲委派的关键
            try {
                c = findClass(name);
            } catch (ClassNotFoundException e) {
                // 3. 自己加载失败,才尝试父加载器
                // 可以根据需要决定是否抛出异常
                return super.loadClass(name, resolve);
            }
            
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 从指定路径加载类文件
        byte[] data = loadClassData(name);
        if (data == null) {
            throw new ClassNotFoundException(name);
        }
        return defineClass(name, data, 0, data.length);
    }
    
    private byte[] loadClassData(String className) {
        // 读取.class文件
        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();
        } catch (Exception e) {
            return null;
        }
    }
}

2. 实现场景:插件化架构

java

复制

下载

复制代码
// 插件管理器
public class PluginManager {
    
    private Map<String, PluginClassLoader> pluginLoaders = new HashMap<>();
    
    public void loadPlugin(String pluginId, String pluginPath) {
        // 为每个插件创建独立的类加载器
        PluginClassLoader loader = new PluginClassLoader(pluginPath);
        pluginLoaders.put(pluginId, loader);
        
        // 加载插件入口类
        Class<?> pluginClass = loader.loadClass("com.plugin.Main");
        Plugin plugin = (Plugin) pluginClass.newInstance();
        plugin.initialize();
    }
    
    // 插件类加载器
    class PluginClassLoader extends URLClassLoader {
        
        public PluginClassLoader(String pluginPath) {
            // 关键:父加载器设置为null或特殊的类加载器
            // 避免插件类污染主应用的类空间
            super(new URL[0], null);
            
            // 添加插件路径
            addURL(new File(pluginPath).toURI().toURL());
        }
        
        @Override
        public Class<?> loadClass(String name, boolean resolve) 
                throws ClassNotFoundException {
            
            // 1. 检查是否是系统类(java.、javax.等)
            if (name.startsWith("java.") || name.startsWith("javax.")) {
                return getSystemClassLoader().loadClass(name);
            }
            
            // 2. 先在自己的插件路径查找(打破双亲委派)
            try {
                Class<?> c = findClass(name);
                if (c != null) return c;
            } catch (ClassNotFoundException e) {
                // 继续
            }
            
            // 3. 共享的公共库类委托给共享类加载器
            if (isSharedLibrary(name)) {
                return getSharedClassLoader().loadClass(name);
            }
            
            // 4. 最后才使用标准双亲委派
            return super.loadClass(name, resolve);
        }
    }
}

四、打破双亲委派的原理分析

1. 原理总结

java

复制

下载

复制代码
// 打破双亲委派的本质是修改 loadClass() 方法的逻辑
public class BreakingParentDelegationClassLoader extends ClassLoader {
    
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        // 标准双亲委派的顺序:
        // 1. findLoadedClass()          // 检查是否已加载
        // 2. parent.loadClass()         // 委托父加载器 ← 通常在这里打破
        // 3. findClass()                // 自己加载
        
        // 打破双亲委派的常见方式:
        // 方式1:先自己加载,失败才委托父加载器(反向委托)
        // 方式2:某些包自己加载,某些包委托父加载器(条件委托)
        // 方式3:完全自己加载,不委托父加载器(隔离)
        // 方式4:委托给非父类的其他加载器(网状委托)
    }
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

2. 打破双亲委派的风险

java

复制

下载

复制代码
class RisksOfBreakingParentDelegation {
    
    // 风险1:类型安全破坏
    void typeSafetyIssue() {
        // 同一个类被不同类加载器加载,被认为是不同的类型
        Class<?> class1 = loader1.loadClass("com.example.MyClass");
        Class<?> class2 = loader2.loadClass("com.example.MyClass");
        // class1 != class2,即使字节码完全相同
        // 导致 ClassCastException
    }
    
    // 风险2:内存泄漏
    void memoryLeakIssue() {
        // 每个类加载器都有自己的命名空间
        // 无法卸载类会导致 PermGen/Metaspace OOM
        // 特别是热部署场景
    }
    
    // 风险3:资源浪费
    void resourceWasteIssue() {
        // 同一个类被多个类加载器加载多次
        // 浪费内存和加载时间
    }
    
    // 风险4:类版本冲突
    void versionConflictIssue() {
        // 不同模块使用不同版本的库
        // 通过不同类加载器隔离
        // 但传递对象时可能遇到兼容性问题
    }
}

3. 最佳实践

java

复制

下载

复制代码
class BestPractices {
    
    // 1. 明确打破双亲委派的目的
    enum BreakingPurpose {
        MODULE_ISOLATION,      // 模块隔离
        HOT_DEPLOYMENT,        // 热部署
        PLUGIN_ARCHITECTURE,   // 插件架构
        VERSION_ISOLATION,     // 版本隔离
        SECURITY_ISOLATION     // 安全隔离
    }
    
    // 2. 控制破坏范围
    class ControlledBreakingClassLoader extends ClassLoader {
        @Override
        protected Class<?> loadClass(String name, boolean resolve) {
            // 只对特定包打破双亲委派
            if (shouldBreakDelegation(name)) {
                return loadClassWithoutDelegation(name);
            }
            // 其他包使用标准双亲委派
            return super.loadClass(name, resolve);
        }
        
        private boolean shouldBreakDelegation(String className) {
            // 明确哪些包需要特殊处理
            return className.startsWith("com.plugin.") ||
                   className.startsWith("com.dynamic.");
        }
    }
    
    // 3. 提供类加载器通信机制
    interface ClassLoaderBridge {
        // 不同类加载器间传递对象的机制
        Object transferObject(Object obj, ClassLoader targetLoader);
        
        // 类型转换检查
        boolean isCompatible(Class<?> sourceType, Class<?> targetType);
    }
}

五、Java 9+模块化系统的影响

1. 模块化系统下的类加载

java

复制

下载

复制代码
// Java 9+的模块化类加载
public class ModuleClassLoader extends ClassLoader {
    
    // 模块化系统引入了新的类加载规则
    // 1. 模块声明依赖关系
    // 2. 类加载基于模块依赖图
    // 3. 强封装性:非导出包对外不可见
    
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        // 在模块化系统中:
        // 1. 类加载器委托关系被模块依赖关系替代
        // 2. 模块路径替代了类路径
        // 3. 服务加载通过模块声明实现
        
        // 模块化实际上没有打破双亲委派
        // 而是引入了更精细的模块隔离机制
    }
}

2. 模块化与双亲委派的兼容

java

复制

下载

复制代码
// 模块化系统兼容双亲委派
class ModuleCompatibility {
    
    // Java 9+ 中双亲委派仍然存在
    // 但增加了模块化约束:
    
    // 1. 启动类加载器 → 加载java.base等核心模块
    // 2. 平台类加载器 → 加载其他平台模块
    // 3. 应用类加载器 → 加载应用模块
    
    // 模块化系统的特点:
    // - 模块间的类可见性由module-info.java控制
    // - 服务发现通过provides/uses声明
    // - 打破了传统的"全有或全无"的类可见性
}

六、总结

打破双亲委派的核心场景:

  1. SPI机制:父加载器需要调用子加载器加载实现类

  2. 应用服务器:Web应用隔离、热部署、版本隔离

  3. OSGi模块化:Bundle间的平级类加载委托

  4. 热部署工具:动态代码更新和重加载

  5. 插件化架构:插件隔离和动态加载

设计原则:

  1. 明确目的:只有在必要时才打破双亲委派

  2. 控制范围:只对特定包或场景打破委派

  3. 考虑兼容性:处理类型安全和版本冲突

  4. 资源管理:注意类加载器的生命周期和内存泄漏

  5. 利用新特性:Java 9+的模块化提供更优雅的解决方案

双亲委派模型的破坏是Java生态灵活性的体现,但需要谨慎使用。理解其原理和场景,才能在适当的时候做出正确的设计选择。

相关推荐
冰暮流星1 天前
javascript赋值运算符
开发语言·javascript·ecmascript
谢娘蓝桥1 天前
adi sharc c/C++ 语言指令优化
开发语言·c++
程序员小远1 天前
UI自动化测试框架:PO模式+数据驱动
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
小毛驴8501 天前
Maven同时配置阿里云仓库和私有仓库
java·阿里云·maven
2501_941805311 天前
从微服务网关到统一安全治理的互联网工程语法实践与多语言探索
前端·python·算法
刘97531 天前
【第25天】25c#今日小结
java·开发语言·c#
不如打代码KK1 天前
Springboot如何解决跨域问题?
java·spring boot·后端
Chris_12191 天前
Halcon学习笔记-Day5
人工智能·笔记·python·学习·机器学习·halcon
豆沙沙包?1 天前
2026年--Lc330-394. 字符串解码(栈)--java版
java·开发语言