Just do it
专注做自己的事,有想做的事就去做好了

类加载器加载的过程

🔴 双亲委派机制的执行流程

  1. 委派阶段(自下而上):

    • 当需要加载一个类时,首先由当前ClassLoader(通常是Application ClassLoader)接收请求
    • 当前ClassLoader检查自己是否已经加载过该类,如果已加载则直接返回
    • 如果未加载,则委派给父ClassLoader处理
    • 逐级向上委派:Application ClassLoader → Extension ClassLoader → Bootstrap ClassLoader
    • 直到委派到Bootstrap ClassLoader(顶层,没有父ClassLoader)
  2. 加载阶段(自上而下):

    • Bootstrap ClassLoader首先尝试加载该类
      • 负责加载核心类库(rt.jar、charsets.jar等)
      • 如果该类属于核心类库范围,则加载成功并返回
      • 如果不属于其负责范围,则加载失败
    • 如果Bootstrap ClassLoader加载失败,则由Extension ClassLoader尝试加载
      • 负责加载扩展类库(lib/ext目录下的jar包)
      • 如果属于扩展类库范围,则加载成功并返回
      • 如果不属于其负责范围,则加载失败
    • 如果Extension ClassLoader也加载失败,则由Application ClassLoader尝试加载
      • 负责加载用户类路径(classpath)下的类
      • 通常能够加载成功,因为用户类都在classpath中
  3. 关键特点

    • 安全性:确保核心类库不被用户类覆盖
    • 唯一性:同一个类只会被一个ClassLoader加载一次
    • 层次性:每个ClassLoader都有明确的职责范围

全盘负责机制

🔴 全盘负责(Full Responsibility):当一个类加载器负责加载某个类时,它不仅要加载该类本身,还要负责加载该类所依赖的所有其他类(包括其父类、接口、字段类型、方法参数类型、方法返回类型等)。

🟠 全盘负责的具体表现:

  1. 依赖类加载

    • 当加载类A时,如果A继承自类B,那么类加载器必须同时负责加载类B
    • 如果A实现了接口C,那么类加载器必须同时负责加载接口C
    • 如果A的字段类型是类D,那么类加载器必须同时负责加载类D
  2. 递归加载

    • 加载过程是递归的:加载A需要加载B,加载B又可能需要加载E
    • 整个依赖链上的所有类都由同一个ClassLoader负责加载
  3. 一致性保证

    • 确保一个类的所有依赖类都由同一个ClassLoader加载
    • 避免不同ClassLoader加载同一个类导致类型不一致的问题

🟡 全盘负责的重要性:

  • 类型安全:防止同一个类被不同ClassLoader加载造成类型冲突
  • 内存效率:避免重复加载相同的类
  • 运行时稳定:确保类之间引用关系的正确性

🔴 当前入口ClassLoader无法加载涉及类的处理机制

🔴 核心问题:如果当前入口的ClassLoader无法加载当前类所涉及的类会怎么处理?

🟠 处理流程详解:

  1. 委派机制启动

    • 当前ClassLoader(入口ClassLoader)首先尝试加载目标类
    • 如果无法加载,启动双亲委派机制
    • 按照层次结构向上委派:Application → Extension → Bootstrap
  2. 逐级尝试加载

    • Bootstrap ClassLoader尝试

      • 如果目标类属于核心类库(如java.lang.*),Bootstrap成功加载
      • 如果不属于核心类库范围,Bootstrap返回null(表示加载失败)
    • Extension ClassLoader尝试

      • 如果Bootstrap失败,Extension尝试在ext目录加载
      • 如果目标类属于扩展类库,Extension成功加载
      • 如果不属于扩展类库范围,Extension返回null
    • Application ClassLoader尝试

      • 如果Extension也失败,Application尝试在classpath加载
      • 如果目标类在用户类路径中,Application成功加载
      • 如果不在classpath中,Application返回null
  3. 最终处理结果

    🔴 成功情况

    • 某个ClassLoader成功加载了类,返回Class对象
    • 后续对该类的所有引用都使用这个ClassLoader加载的版本

    🔴 失败情况

    • 所有ClassLoader都无法加载目标类
    • 抛出ClassNotFoundException异常
    • 程序执行被中断

🟡 实际应用场景:

  1. 缺少依赖JAR包

    // 如果classpath中缺少某个依赖库
    // 当代码引用该库中的类时,会触发ClassNotFoundException
    import com.example.ExternalClass; // 如果ExternalClass不存在
    
  2. 类路径配置错误

    // 如果JAR包在错误的路径下
    // Application ClassLoader无法找到类文件
    
  3. 版本不匹配

    // 编译时使用的类版本与运行时不一致
    // 可能导致ClassNotFoundException
    

是否可以打破双亲委派

🔴 答案:可以,但需要通过特定的方式,最常见的是重写loadClass()方法。

🟠 打破双亲委派的方法:

  1. 重写loadClass()方法(主要方式):

    • loadClass()方法是双亲委派机制的核心实现
    • findClass()方法只是加载类的具体实现,不会影响委派机制
    • 通过重写loadClass()方法可以完全绕过双亲委派
  2. 使用线程上下文类加载器(SPI机制):

    • 通过Thread.currentThread().setContextClassLoader()设置
    • 主要用于JDBC驱动加载、日志框架等SPI场景
  3. 继承ClassLoader类

    • 不是实现接口,而是继承ClassLoader类
    • 可以自定义类加载逻辑

🟡 打破双亲委派的应用场景:

  1. 热部署和热替换:如OSGi框架、Tomcat的Web应用隔离
  2. 模块化加载:不同模块需要不同版本的同一个类
  3. SPI机制:JDBC驱动加载、日志框架等
  4. 类隔离:防止不同应用之间的类冲突

🟢 风险和注意事项:

  • 安全性风险:可能绕过核心类库的保护机制
  • 类冲突:同一类被多个ClassLoader加载可能导致类型转换异常
  • 内存泄漏:自定义ClassLoader可能导致类无法被GC回收
  • 调试困难:打破标准机制会增加问题排查的复杂度

为什么我们的类加载器要分层

🔴 简单理解:就像公司的组织架构一样,分层管理更安全更高效

🟠 主要原因:

  1. 安全性 - 保护核心类库

    • 防止你写一个恶意的java.lang.String类覆盖系统的String
    • 就像银行金库有多层防护一样
  2. 职责分工 - 各司其职,便于排查问题

    • Bootstrap:管JDK核心类(java.lang.*等)
    • Extension:管扩展类库
    • Application:管你的应用程序类
    • 出现ClassNotFoundException时,能快速定位是哪一层的问题
    • 就像不同部门管不同的事,出问题时知道找谁
posted on 2025-10-14 20:02  Ireck  阅读(8)  评论(0)    收藏  举报