JVM 类加载器之间的层次关系,以及类加载的委托机制

JVM 类加载器之间存在一种层次关系,通常被称为双亲委派模型 (Parent Delegation Model)。这种层次关系和委托机制是 Java 类加载机制的核心,对于保证 Java 程序的安全性和避免类冲突至关重要。

1. 类加载器的层次关系:

JVM 中的类加载器(ClassLoader)主要分为以下几种,它们之间存在自顶向下的层次关系(父子关系,但不是继承关系,而是组合关系):

  • 启动类加载器 (Bootstrap Class Loader):

    • 最顶层的类加载器。
    • 负责加载 Java 核心类库 ,例如 java.langjava.utiljava.io 等包中的类(通常位于 <JAVA_HOME>/jre/lib 目录下的 rt.jarresources.jar 等)。
    • 用 C/C++ 实现,是 JVM 的一部分,不是 Java 类。
    • 没有父类加载器。
    • 无法通过 Java 代码直接获取到启动类加载器。
  • 扩展类加载器 (Extension Class Loader):

    • 父类加载器是启动类加载器。
    • 负责加载 Java 扩展类库 (通常位于 <JAVA_HOME>/jre/lib/ext 目录下的 jar 包,或者由 java.ext.dirs 系统属性指定的目录)。
    • sun.misc.Launcher$ExtClassLoader 的实例(Java 类)。
  • 应用程序类加载器 (Application Class Loader / System Class Loader):

    • 父类加载器是扩展类加载器。
    • 负责加载应用程序的类(classpath 下的类,包括你写的代码、第三方库等)。
    • sun.misc.Launcher$AppClassLoader 的实例(Java 类)。
    • 通常是默认的类加载器。 可以通过 ClassLoader.getSystemClassLoader() 获取。
  • 自定义类加载器 (User-Defined Class Loader):

    • 父类加载器通常是应用程序类加载器(也可以是其他自定义类加载器)。
    • 由开发者自定义,继承 java.lang.ClassLoader 类。
    • 用于实现特殊的类加载逻辑 ,例如:
      • 从网络加载类。
      • 从数据库加载类。
      • 对类进行加密和解密。
      • 实现热部署(HotSwap)。
      • 实现模块化(OSGi)。

类加载器层次关系图示:

复制代码
                     +-----------------------------+
                     |  Bootstrap Class Loader     |  (C/C++)
                     +-----------------------------+
                                  ↑
                                  |  (Parent)
                                  |
                     +-----------------------------+
                     |  Extension Class Loader     |  (sun.misc.Launcher$ExtClassLoader)
                     +-----------------------------+
                                  ↑
                                  |  (Parent)
                                  |
                     +-----------------------------+
                     | Application Class Loader    |  (sun.misc.Launcher$AppClassLoader)
                     +-----------------------------+
                                  ↑
                                  |  (Parent)
                                  |
                     +-----------------------------+
                     |  User-Defined Class Loader  |  (extends java.lang.ClassLoader)
                     +-----------------------------+

2. 双亲委派模型 (Parent Delegation Model):

  • 工作原理:

    1. 当一个类加载器需要加载类时,它首先不会自己尝试加载,而是 委托给它的父类加载器 去加载。
    2. 父类加载器会检查自己是否已经加载过该类。如果已经加载,则直接返回 Class 对象。
    3. 如果父类加载器没有加载过该类,则会尝试加载。如果父类加载器在其搜索范围内找到了该类,则加载成功,并返回 Class 对象。
    4. 如果父类加载器无法加载该类(在其搜索范围内找不到该类),则 将加载请求返回给子类加载器
    5. 子类加载器尝试加载该类。如果子类加载器能够加载,则加载成功,并返回 Class 对象。
    6. 如果子类加载器也无法加载该类,则抛出 ClassNotFoundException 异常。
  • 特例: 启动类加载器没有父类加载器,它会直接尝试加载。

  • 代码示例 (简化版):

    java 复制代码
    // java.lang.ClassLoader 中的 loadClass 方法 (简化版)
    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. 如果没有父类加载器 (到达了启动类加载器),则调用 findBootstrapClassOrNull
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 父类加载器无法加载该类
                }
    
                if (c == null) {
                    // 4. 如果父类加载器无法加载,则调用 findClass 方法尝试自己加载
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c); // 链接类 (可选)
            }
            return c;
        }
    }
    • findClass() 方法是需要子类加载器重写的方法, 用于实现自定义的类加载逻辑.

3. 双亲委派模型的优点:

  • 避免类的重复加载: 同一个类只会被加载一次,避免了命名冲突和资源浪费。
  • 保证 Java 核心类库的安全性: 用户自定义的类加载器无法加载或替换 Java 核心类库中的类(例如 java.lang.Objectjava.lang.String),因为核心类库总是由启动类加载器加载。这可以防止恶意代码篡改核心类库,破坏 JVM 的安全性。
  • 命名空间隔离: 不同的类加载器加载的类位于不同的命名空间, 可以防止类名冲突.

4. 破坏双亲委派模型:

虽然双亲委派模型有很多优点,但在某些情况下,可能需要破坏双亲委派模型,例如:

  • JDBC、JNDI、JAXP 等 SPI (Service Provider Interface) 机制:

    • 这些 API 的核心接口是由启动类加载器加载的,但具体的实现类通常由应用程序类加载器或自定义类加载器加载。
    • 为了解决这个问题,Java 引入了线程上下文类加载器 (Thread Context ClassLoader)。
    • 可以通过 Thread.currentThread().getContextClassLoader() 获取线程上下文类加载器。
    • 例如,JDBC 驱动程序通常由应用程序类加载器加载,但 JDBC API (如 java.sql.DriverManager) 需要能够加载这些驱动程序。DriverManager 会使用线程上下文类加载器来加载驱动程序。
  • OSGi (Open Service Gateway initiative):

    • OSGi 是一个模块化系统,每个模块(bundle)都有自己的类加载器。
    • OSGi 的类加载器模型不是严格的双亲委派模型,而是更复杂的网状结构。
  • 热部署 (HotSwap):

    • 在应用程序运行时,动态地替换或更新类。
    • 通常需要自定义类加载器,并破坏双亲委派模型。
  • Tomcat:

    • Tomcat 为了实现 web 应用之间的隔离,以及共享库的加载,破坏了双亲委派模型。
    • 每个 web 应用都有自己的类加载器 (WebAppClassLoader)。

总结:

JVM 类加载器之间存在层次关系(启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器),并通过双亲委派模型协同工作。双亲委派模型保证了类加载的顺序和安全性,避免了类的重复加载,并防止了核心类库被篡改。 在某些特殊情况下,可能需要破坏双亲委派模型(例如,SPI、OSGi、热部署)。

相关推荐
大刀爱敲代码2 小时前
基础算法01——二分查找(Binary Search)
java·算法
追风少年1554 小时前
常见中间件漏洞之一 ----【Tomcat】
java·中间件·tomcat
yang_love10114 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
郑州吴彦祖7725 小时前
【Java】UDP网络编程:无连接通信到Socket实战
java·网络·udp
spencer_tseng5 小时前
eclipse [jvm memory monitor] SHOW_MEMORY_MONITOR=true
java·jvm·eclipse
鱼樱前端5 小时前
mysql事务、行锁、jdbc事务、数据库连接池
java·后端
Hanson Huang6 小时前
23种设计模式-外观(Facade)设计模式
java·设计模式·外观模式·结构型设计模式
Hanson Huang6 小时前
23种设计模式-生成器(Builder)设计模式
java·设计模式·生成器模式
hakesashou6 小时前
python多线程和多进程的区别有哪些
java·开发语言·jvm