当前位置: 首页 > news >正文

Java类加载器与双亲委派模型

文章目录

  • 什么是类加载器
  • Java类加载器体系
    • 启动类加载器
    • 扩展类加载器
    • 应用类加载器
  • 双亲委派模型
    • 双亲委派的源码实现
    • 双亲委派的好处
  • 如何打破双亲委派模型
    • 直接重写loadClass方法(自定义)

什么是类加载器

简单来说,类加载器就是负责把.class文件加载到JVM内存中,并转换成可以被JVM使用的Class对象的组件。

Java类加载器体系

Java中的类加载器按照层次结构分为几个级别,形成了一个树状的结构。

启动类加载器

这是最顶层的类加载器,负责加载Java的核心类库,比如rt.jar中的所有类(String、Object等)。它是用C++实现的,所以在Java代码中看不到具体的类,显示为null。

你可以通过这个方式查看启动类加载器加载了哪些路径:

public class BootstrapClassLoaderDemo {public static void main(String[] args) {System.out.println("启动类加载器加载的路径:");String[] bootClassPath = System.getProperty("sun.boot.class.path").split(";");for (String path : bootClassPath) {System.out.println(path);}}
}

扩展类加载器

扩展类加载器负责加载Java的扩展库,默认加载JAVA_HOME/lib/ext目录下的jar包。

public class ExtensionClassLoaderDemo {public static void main(String[] args) {ClassLoader extClassLoader = ClassLoader.getSystemClassLoader().getParent();System.out.println("扩展类加载器: " + extClassLoader);System.out.println("扩展类加载器加载的路径:");String[] extDirs = System.getProperty("java.ext.dirs").split(";");for (String dir : extDirs) {System.out.println(dir);}}
}

应用类加载器

这个是我们最常接触的类加载器,负责加载用户类路径(classpath)下的类。我们项目中写的类基本都是由它加载的。

public class ApplicationClassLoaderDemo {public static void main(String[] args) {ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();System.out.println("应用类加载器: " + appClassLoader);System.out.println("应用类加载器加载的路径:");String[] classPaths = System.getProperty("java.class.path").split(";");for (String path : classPaths) {System.out.println(path);}}
}

双亲委派模型

双亲委派模型是Java类加载器的核心机制,它的工作流程是这样的:

  1. 当一个类加载器收到类加载请求时,首先不会自己去加载这个类
  2. 而是把请求委派给父类加载器去完成
  3. 每一层都是如此,最终请求会传递到启动类加载器
  4. 只有当父加载器无法完成加载时,子加载器才会尝试自己加载

双亲委派的源码实现

我们来看看ClassLoader中loadClass方法的源码:

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 {// 父加载器为null,委派给启动类加载器c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 父加载器无法加载}if (c == null) {// 父加载器无法加载,自己尝试加载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;}
}

双亲委派的好处

避免重复加载:如果一个类已经被父加载器加载过了,就不需要子加载器再加载一次。

安全性:核心类库只能由启动类加载器加载,避免了用户自定义的类替换掉核心类库的风险。

如何打破双亲委派模型

虽然双亲委派模型设计得很好,但现实总是复杂的。在某些场景下,我们确实需要打破这个规则。要打破双亲委派,关键是重写loadClass方法而不是findClass方法。因为双亲委派的逻辑就写在loadClass里面。
看看正常的双亲委派流程:

// ClassLoader.loadClass的简化逻辑
protected Class<?> loadClass(String name, boolean resolve) {// 1. 先检查是否已经加载Class<?> c = findLoadedClass(name);if (c == null) {// 2. 委派给父加载器(这里是关键!)if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}// 3. 父加载器加载失败,自己尝试if (c == null) {c = findClass(name);}}return c;
}

要打破双亲委派,我们就要改变这个流程,不再无条件地先委派给父加载器。

直接重写loadClass方法(自定义)

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 1. 先检查是否已经加载过Class<?> clazz = findLoadedClass(name);if (clazz != null) {return clazz;}// 2. 这里是关键:我们改变了加载顺序// 对于我们关心的包,先尝试自己加载if (name.startsWith("com.mycompany.")) {System.out.println("自定义加载器优先加载: " + name);try {clazz = findClass(name);if (clazz != null) {if (resolve) {resolveClass(clazz);}return clazz;}} catch (ClassNotFoundException e) {// 自己加载失败,再委派给父加载器System.out.println("自己加载失败,委派给父加载器: " + name);}}// 3. 对于系统类或者自己加载失败的类,还是走双亲委派if (getParent() != null) {clazz = getParent().loadClass(name);} else {clazz = getSystemClassLoader().loadClass(name);}if (resolve) {resolveClass(clazz);}return clazz;}}
http://www.dtcms.com/a/301551.html

相关文章:

  • n8n “Run Once for All Items“和“Run Once for Each Item“区别
  • 深度学习中的计算图与自动微分原理:静态图与动态图的实现差异
  • sd Function 学习笔记
  • BeautifulSoup 使用详解与实战示例
  • WAIC 2025 热点解读:如何构建 AI 时代的“视频神经中枢”?
  • WordPress 网站中的“mu-plugins”隐藏后门
  • [每周一更]-(第152期):Go中的CAS(Compare-And-Swap)锁原理详解
  • Java面试宝典:MySQL性能优化
  • ES6模块详解:核心语法与最佳实践
  • 编码器和解码器风格的Transformer架构
  • 使用vue2和 element-ui 做一个点餐收银台系统前端静态项目
  • 数据江湖的“三国演义”:数据仓库、数据湖与湖仓一体的全景对比
  • Gradio全解8——ChatInterfaceChatbot:聊天界面类与聊天机器人(4)——返回复杂响应与直接修改Chatbot值
  • Java Ai(day03)
  • 【秋招笔试】7月26日科大讯飞秋招第一题
  • 【最新最完整】SpringAI-1.0.0开发MCP Server,搭建MCP Client 实战笔记(进阶+详细+完整代码)
  • AI Agent学习
  • MyBatis-Plus IService 接口全量方法实现与测试(续)
  • 【c++】从 “勉强能用” 到 “真正好用”:中文问答系统的 200 行关键优化——关于我用AI编写了一个聊天机器人……(16)
  • 中级全栈工程师笔试题
  • DP之背包基础
  • 详解力扣高频SQL50题之180. 连续出现的数字【困难】
  • CPA会计-5- 投资性房地产
  • 逆向入门(42)程序逆向篇-riijj_cm_20041121
  • 生态环境大数据技术专业的深度解析
  • 物理实验仿真平台设计与实现(抛体运动与电磁场交互)
  • 构建可扩展的状态系统:基于 ArkTS 的模块化状态管理设计与实现
  • MPI环形AllReduce算法实现与深度解析
  • lombok插件@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor的区别
  • RS485 半双工系统中 DE 控制端默认 0 的技术原理与工程实践