JAVA--双亲委派机制

目录

什么是双亲委派机制

类加载器的层级结构

[1. 启动类加载器(Bootstrap ClassLoader)](#1. 启动类加载器(Bootstrap ClassLoader))

[2. 扩展类加载器(Extension ClassLoader)](#2. 扩展类加载器(Extension ClassLoader))

[3. 应用程序类加载器(Application ClassLoader)](#3. 应用程序类加载器(Application ClassLoader))

[4. 自定义类加载器(Custom ClassLoader)](#4. 自定义类加载器(Custom ClassLoader))

双亲委派机制的工作原理

为什么需要双亲委派机制

[1. 避免类的重复加载](#1. 避免类的重复加载)

[2. 保证Java核心API的安全性](#2. 保证Java核心API的安全性)

[3. 保证类的唯一性](#3. 保证类的唯一性)

双亲委派机制的源码分析

双亲委派机制的破坏

[1. 自定义类加载器](#1. 自定义类加载器)

[2. 线程上下文类加载器](#2. 线程上下文类加载器)

[3. 热替换和热部署](#3. 热替换和热部署)

实际应用场景

[1. Web应用服务器](#1. Web应用服务器)

[2. OSGi框架](#2. OSGi框架)

[3. 模块化系统](#3. 模块化系统)

总结


什么是双亲委派机制

双亲委派机制(Parents Delegation Model)是JVM中类加载器的一种工作机制。当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器无法完成加载请求时,子类加载器才会尝试自己去加载。

这种机制确保了Java核心API的类不会被随意替换,维护了Java运行环境的安全性和稳定性。

类加载器的层级结构

Java中的类加载器按照层级关系分为以下几种:

1. 启动类加载器(Bootstrap ClassLoader)

  • 位置:JVM内部实现,由C++代码实现
  • 作用 :加载Java核心类库(如java.lang.*java.util.*等)
  • 路径$JAVA_HOME/lib目录下的类库
  • 特点:最顶层的类加载器,没有父类加载器

2. 扩展类加载器(Extension ClassLoader)

  • 位置sun.misc.Launcher$ExtClassLoader
  • 作用:加载扩展类库
  • 路径$JAVA_HOME/lib/ext目录下的类库
  • 父类加载器:启动类加载器

3. 应用程序类加载器(Application ClassLoader)

  • 位置sun.misc.Launcher$AppClassLoader
  • 作用:加载应用程序类路径(ClassPath)上的类
  • 路径:环境变量ClassPath指定的路径
  • 父类加载器:扩展类加载器
  • 特点:也称为系统类加载器

4. 自定义类加载器(Custom ClassLoader)

  • 作用:用户根据需要自定义的类加载器
  • 父类加载器:通常是应用程序类加载器
java 复制代码
// 查看类加载器层级结构的示例代码
public class ClassLoaderHierarchy {
    public static void main(String[] args) {
        // 获取当前类的类加载器
        ClassLoader classLoader = ClassLoaderHierarchy.class.getClassLoader();
        System.out.println("当前类的类加载器:" + classLoader);
        
        // 获取父类加载器
        ClassLoader parentClassLoader = classLoader.getParent();
        System.out.println("父类加载器:" + parentClassLoader);
        
        // 获取祖父类加载器
        ClassLoader grandParentClassLoader = parentClassLoader.getParent();
        System.out.println("祖父类加载器:" + grandParentClassLoader);
        
        // 输出结果:
        // 当前类的类加载器:sun.misc.Launcher$AppClassLoader@2a139a55
        // 父类加载器:sun.misc.Launcher$ExtClassLoader@15db9742
        // 祖父类加载器:null (Bootstrap ClassLoader由C++实现,在Java中显示为null)
    }
}

双亲委派机制的工作原理

双亲委派机制的工作流程如下:

  1. 接收加载请求:类加载器接收到类加载请求

  2. 向上委派:不立即加载,而是委派给父类加载器

  3. 递归委派:父类加载器继续向上委派,直到启动类加载器

  4. 尝试加载:启动类加载器尝试加载类

  5. 向下返回:如果加载失败,返回给子类加载器尝试加载

  6. 最终加载:直到某个类加载器成功加载类或全部失败

    graph TD
    A[自定义类加载器] --> B[应用程序类加载器]
    B --> C[扩展类加载器]
    C --> D[启动类加载器]
    D --> E{能否加载?}
    E -->|能| F[加载完成]
    E -->|不能| G[委派给子类加载器]
    G --> H{扩展类加载器能否加载?}
    H -->|能| I[加载完成]
    H -->|不能| J[委派给子类加载器]
    J --> K{应用程序类加载器能否加载?}
    K -->|能| L[加载完成]
    K -->|不能| M[委派给子类加载器]
    M --> N{自定义类加载器能否加载?}
    N -->|能| O[加载完成]
    N -->|不能| P[抛出ClassNotFoundException]

为什么需要双亲委派机制

1. 避免类的重复加载

如果没有双亲委派机制,每个类加载器都可能加载同一个类,导致内存中存在多个相同的类对象。

2. 保证Java核心API的安全性

防止核心API被恶意替换。例如,如果有人自定义了一个java.lang.String类,通过双亲委派机制,最终会由启动类加载器加载JDK中的String类,而不是用户自定义的类。

3. 保证类的唯一性

在JVM中,类的唯一性是由类加载器和类的全限定名共同决定的。双亲委派机制确保了同一个类只会被同一个类加载器加载一次。

双亲委派机制的源码分析

让我们来看看ClassLoader类中loadClass方法的实现:

java 复制代码
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 首先检查该类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 如果有父类加载器,委派给父类加载器加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 如果没有父类加载器,说明是启动类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果父类加载器抛出ClassNotFoundException
                // 说明父类加载器无法完成加载请求
            }

            if (c == null) {
                // 如果父类加载器无法加载,则调用自己的findClass方法进行加载
                long t1 = System.nanoTime();
                c = findClass(name);
                
                // 记录加载时间统计
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

双亲委派机制的破坏

虽然双亲委派机制很重要,但在某些场景下需要被破坏:

1. 自定义类加载器

通过重写loadClass方法来改变类加载的行为:

java 复制代码
public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
        throws ClassNotFoundException {
        // 首先检查是否已经加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 对于自定义的类,直接由当前类加载器加载
            if (name.startsWith("com.example.")) {
                c = findClass(name);
            } else {
                // 其他类仍然遵循双亲委派
                c = super.loadClass(name, resolve);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 实现自定义的类加载逻辑
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }
    
    private byte[] loadClassData(String name) {
        // 从自定义位置加载类的字节码
        // 这里可以从网络、数据库等位置加载
        return null; // 简化示例
    }
}

2. 线程上下文类加载器

在某些情况下,父类加载器需要加载由子类加载器加载的类,这时可以使用线程上下文类加载器:

java 复制代码
public class ContextClassLoaderExample {
    public static void main(String[] args) {
        // 获取当前线程的上下文类加载器
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        System.out.println("上下文类加载器:" + contextClassLoader);
        
        // 设置自定义的上下文类加载器
        Thread.currentThread().setContextClassLoader(new CustomClassLoader());
        
        // 在某些框架中,会使用上下文类加载器来加载类
        // 例如:JDBC驱动加载、Spring容器等
    }
}

3. 热替换和热部署

在开发环境中,为了实现热替换功能,需要破坏双亲委派机制:

java 复制代码
public class HotSwapClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
        throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 对于需要热替换的类,每次都重新加载
            if (isHotSwapClass(name)) {
                c = findClass(name);
            } else {
                c = super.loadClass(name, resolve);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
    
    private boolean isHotSwapClass(String name) {
        // 判断是否是需要热替换的类
        return name.startsWith("com.example.hotswap");
    }
}

实际应用场景

1. Web应用服务器

Tomcat等Web服务器为了实现应用隔离,每个Web应用都有自己的类加载器:

复制代码
// Tomcat的类加载器层级结构
// Bootstrap ClassLoader
//     |
// System ClassLoader
//     |
// Common ClassLoader
//     |
// Catalina ClassLoader  Shared ClassLoader
//                           |
//                      WebApp ClassLoader

2. OSGi框架

OSGi框架完全破坏了双亲委派机制,实现了网状的类加载器结构。

3. 模块化系统

Java 9的模块系统也对双亲委派机制进行了一定的改进。

总结

双亲委派机制是Java类加载器的核心机制,它具有以下特点:

优点:

  • 避免类的重复加载
  • 保证Java核心API的安全性
  • 维护类的唯一性

缺点:

  • 在某些场景下过于严格,需要被破坏
  • 可能导致类加载的性能问题

适用场景:

  • 大部分标准Java应用
  • 需要保证类加载安全性的场景

破坏场景:

  • 自定义类加载器
  • 热替换和热部署
  • 模块化系统
  • Web应用服务器