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

【JVM】双亲委派

你这份总结整体是对的,核心就是围绕三个问题:

类加载器是谁?它负责把 class 文件加载进 JVM。

双亲委派是什么?它规定类加载器加载类时,先让父加载器尝试加载。

为什么要破坏双亲委派?因为有些场景需要类隔离、多版本共存。


一、什么是类加载器?

类加载器可以理解成:

JVM 中负责把.class字节码文件加载到内存中的组件。

比如你写了一个类:

publicclassUserService{}

编译后会生成:

UserService.class

程序运行时,JVM 不会一开始就把所有 class 文件都加载进来,而是用到哪个类,就由类加载器把哪个类加载进 JVM 内存

加载进去之后,JVM 才能创建对象、调用方法、执行代码。


二、类加载器有哪些?

常见有四类。

1. Bootstrap ClassLoader:启动类加载器

它是最顶层的类加载器。

主要加载 Java 最核心的类,比如:

java.lang.Stringjava.lang.Objectjava.util.ArrayList

这些类来自 JDK 的核心类库。

在 Java 8 里,主要加载:

JAVA_HOME/jre/lib

下面的核心类库。

它比较特殊,不是 Java 写的,而是 C/C++ 实现的,所以我们在 Java 代码中一般看不到它。

例如:

System.out.println(String.class.getClassLoader());

输出通常是:

null

这个null不是说没有类加载器,而是表示它由Bootstrap ClassLoader加载。


2. Extension ClassLoader:扩展类加载器

它负责加载 JDK 扩展目录下的类。

在 Java 8 中,主要是:

JAVA_HOME/jre/lib/ext

它的父加载器是 Bootstrap ClassLoader。

不过需要注意,Java 9 以后引入了模块化机制,Extension ClassLoader 已经被 Platform ClassLoader 替代了

面试中如果讲 Java 8 的类加载器体系,继续说 Extension ClassLoader 是可以的。


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

这个最常见。

它负责加载我们自己写的业务代码,以及项目依赖的 jar 包。

也就是 classpath 路径下的类,比如:

target/classes lib/*.jar

平时 Spring Boot、普通 Java 项目里面的大部分类,都是它加载的。

它的父加载器是 Extension ClassLoader,也就是:

Application ClassLoader -> Extension ClassLoader -> Bootstrap ClassLoader

4. 自定义类加载器

我们也可以自己写一个类加载器,继承ClassLoader

常见用途有:

1. 加载指定路径下的 class 文件 2. 实现类隔离 3. 实现热部署 4. 实现插件化 5. 实现同一个类的多版本共存 6. 破坏双亲委派

比如 Tomcat、Dubbo、OSGi、一些热部署框架,都会用到自定义类加载器。


三、什么是双亲委派?

双亲委派说的是:

一个类加载器收到类加载请求后,自己不会立刻加载,而是先交给父类加载器去加载。父加载器加载不了,才轮到自己加载。

加载流程大概是:

Application ClassLoader | v Extension ClassLoader | v Bootstrap ClassLoader

假设现在要加载一个类:

java.lang.String

流程是:

1. Application ClassLoader 收到加载请求 2. 它不先自己加载,而是交给 Extension ClassLoader 3. Extension ClassLoader 继续交给 Bootstrap ClassLoader 4. Bootstrap ClassLoader 发现 String 是核心类,自己加载 5. 加载成功,返回结果

所以最终String是由 Bootstrap ClassLoader 加载的。


四、双亲委派不是继承关系

你这句话很重要:

类加载器的父子关系不是继承,而是组合。

什么意思?

不是说:

ApplicationClassLoaderextendsExtensionClassLoader

而是说每个 ClassLoader 内部有一个parent字段,指向自己的父加载器。

大概类似:

classClassLoader{privatefinalClassLoaderparent;}

所以这个“父加载器”是逻辑上的父子关系,不是 Java 类继承上的父子关系。


五、为什么要有双亲委派?

主要有两个好处:安全性唯一性


1. 保证核心类库安全

假设没有双亲委派,我们自己写一个类:

packagejava.lang;publicclassString{}

如果 JVM 优先加载我们自己写的java.lang.String,那就很危险了。

Java 核心类库可能被用户随便替换,比如:

java.lang.Stringjava.lang.Objectjava.util.HashMap

这会导致整个 Java 运行环境混乱。

有了双亲委派之后,加载java.lang.String时,会先交给 Bootstrap ClassLoader。

Bootstrap ClassLoader 发现自己能加载,于是直接加载 JDK 自带的String,不会加载你自己写的那个。

所以双亲委派可以防止用户自定义类冒充核心类。


2. 保证类的唯一性

在 JVM 中,判断两个类是否相同,不只看类的全限定名,还要看加载它的类加载器。

也就是说,一个类的唯一标识是:

类加载器 + 类的全限定名

例如:

com.example.User

如果被同一个类加载器加载一次,那么 JVM 认为它就是同一个类。

如果没有双亲委派,可能多个类加载器都去加载同一个类,导致 JVM 中出现多个“看起来同名,但实际上不同”的类。

双亲委派可以减少重复加载,保证核心类、公共类优先由上层加载器统一加载。


六、双亲委派的执行逻辑

它主要体现在ClassLoaderloadClass()方法里。

简化逻辑如下:

protectedClass<?>loadClass(Stringname,booleanresolve){// 1. 先检查这个类是否已经加载过Class<?>c=findLoadedClass(name);if(c==null){try{if(parent!=null){// 2. 交给父加载器加载c=parent.loadClass(name,false);}else{// 3. 如果没有父加载器,就交给 Bootstrap 加载c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){// 父加载器加载失败}if(c==null){// 4. 父加载器加载不了,才自己加载c=findClass(name);}}returnc;}

核心顺序就是:

先查是否已加载 再交给父加载器 父加载器不行,自己再加载

七、如何破坏双亲委派?

正常情况下,如果你自定义类加载器,一般只需要重写:

findClass()

因为loadClass()仍然保留双亲委派逻辑。

如果你想破坏双亲委派,就要重写:

loadClass()

然后把加载顺序改成:

先自己加载 自己加载不了,再交给父加载器

这就打破了原来的:

先父后子

变成了:

先子后父

这就是破坏双亲委派。


八、为什么 Tomcat 要破坏双亲委派?

这是重点。

一个 Tomcat 可以部署多个 Web 应用,比如:

Tomcat ├── app1 ├── app2 └── app3

假设:

app1 使用 spring-5.0.jar app2 使用 spring-6.0.jar

但是它们里面的类名可能一样,比如:

org.springframework.context.ApplicationContext

如果完全遵循双亲委派,那么公共父加载器一旦加载了某个版本的 Spring,其他应用就只能用这个版本。

这样就会出现问题:

app1 想用 Spring 5 app2 想用 Spring 6 但父加载器只加载了一份 Spring

这就无法实现多应用之间的依赖隔离。

所以 Tomcat 为每个 Web 应用创建一个独立的类加载器:

app1 -> WebappClassLoader1 app2 -> WebappClassLoader2 app3 -> WebappClassLoader3

这样即使类名完全一样:

org.springframework.context.ApplicationContext

只要它们是被不同的类加载器加载的,JVM 就认为它们是不同的类。

也就是:

WebappClassLoader1 + org.springframework.context.ApplicationContext

和:

WebappClassLoader2 + org.springframework.context.ApplicationContext

在 JVM 看来不是同一个类。

这样就实现了:

不同 Web 应用之间的类隔离 不同 Web 应用可以使用不同版本的依赖

九、Tomcat 是完全不遵循双亲委派吗?

不是。

Tomcat 不是简单粗暴地完全破坏双亲委派,而是有选择地破坏。

大概规则是:

Java 核心类:仍然交给 Bootstrap 加载 Tomcat 自己的类:由 Tomcat 的类加载器加载 Web 应用自己的类:优先由自己的 WebappClassLoader 加载 公共共享类:可以由 SharedClassLoader 加载

也就是说,Tomcat 的目标不是“反对双亲委派”,而是为了实现:

Web 应用之间互相隔离 公共类可以共享 核心类仍然安全

十、SharedClassLoader 是干什么的?

你后面这个问题也很关键。

如果每个 Web 应用都有自己的 WebappClassLoader,那确实会有一个问题:

app1 有 spring.jar app2 有 spring.jar app3 有 spring.jar

如果它们用的是同一个版本,那每个应用都各自加载一份,就会浪费内存。

所以 Tomcat 提供了共享类加载器,比如 SharedClassLoader。

你可以把一些公共 jar 放到共享目录中,让 SharedClassLoader 统一加载。

这样多个 Web 应用都可以共享这份类。

好处是:

1. 避免重复加载 2. 节省内存 3. 公共依赖统一管理

但是缺点是:

如果不同 Web 应用需要不同版本的同一个 jar,就不能放到共享目录

否则又会回到版本冲突的问题。


十一、用一句话总结 Tomcat 的类加载机制

可以这样理解:

Tomcat 对 Web 应用自己的类采用“子加载器优先”,从而实现应用隔离;对 Java 核心类仍然遵循双亲委派,保证安全;对公共 jar 可以使用 SharedClassLoader 共享,避免重复加载。


十二、面试版回答

你可以这样答:

类加载器的作用是把 class 字节码文件加载到 JVM 内存中。常见类加载器包括启动类加载器、扩展类加载器、应用程序类加载器以及自定义类加载器。启动类加载器负责加载 JDK 核心类库,扩展类加载器负责加载扩展目录下的类,应用程序类加载器负责加载 classpath 下的业务类和第三方 jar,自定义类加载器可以实现特殊的类加载逻辑。

双亲委派指的是,当一个类加载器收到类加载请求时,不会先自己加载,而是先委派给父加载器,父加载器继续向上委派,最终到启动类加载器。只有当父加载器无法加载时,子加载器才会尝试自己加载。

双亲委派的好处主要有两个:第一是安全性,防止用户自定义类覆盖 Java 核心类;第二是唯一性,避免同一个类被多个类加载器重复加载。

双亲委派主要是在ClassLoaderloadClass()方法中实现的。如果想破坏双亲委派,可以自定义类加载器并重写loadClass()方法,改成优先自己加载。

典型场景是 Tomcat。因为一个 Tomcat 里可以部署多个 Web 应用,不同应用可能依赖同一个类库的不同版本。如果完全遵循双亲委派,就无法实现多版本隔离。因此 Tomcat 为每个 Web 应用提供独立的 WebappClassLoader,让 Web 应用自己的类优先由自己的类加载器加载。这样即使类的全限定名相同,只要类加载器不同,JVM 也认为它们是不同的类,从而实现类隔离和多版本共存。同时,Tomcat 也提供 SharedClassLoader 来加载公共 jar,避免多个应用重复加载相同依赖。

http://www.jsqmd.com/news/967980/

相关文章:

  • 5分钟上手专业级AI换脸工具:roop-unleashed完全指南
  • ncmdumpGUI:如何3步完成网易云音乐NCM格式批量转换
  • 智能驾驶基石:一文读懂L1级辅助驾驶的技术、应用与未来
  • 【CSDN AI数字营销退款指南】:20年IT合规专家亲授3步退费实操+避坑清单
  • SDR、DDR与DDR2内存技术演进:从预取架构到信号完整性的深度解析
  • COM3D2.MaidFiddler实时角色编辑器终极使用指南:打造完美女仆体验
  • Ltx2.3-vrvb 整合包,解压即用,10分钟在本地跑通 AI 视频生成!
  • 电气测量安全:CAT等级与瞬态过电压防护实战指南
  • CSDN AI数字营销看板企业版上线即封神?揭秘那4个不写在官网但写进SLA协议的统计维度——现在看,还剩最后23个试用名额!
  • 工业平行宇宙:07 工厂案例:海尔、汽车工厂
  • WPF中用ViewModel实时生成可编辑TextBox和只读TextBlock并获取输入
  • TM1637四位数码管模块:Arduino简化驱动与项目实战
  • 2026年6月浪琴官方售后网点全网核验白皮书,涵盖地址、热线、服务项目、收费标准完整手册 - 浪琴中国服务中心
  • 【JVM】JIT编译器
  • 大气层系统架构深度解析与高级部署指南
  • W78E58B/W77E516单片机ISP在系统编程实战指南
  • 现代C++:scope_guard 与 defer:通用作用域守卫
  • 用DGL和PyTorch复现HAN:手把手教你搞定异构图注意力网络(附完整代码)
  • 智能手机硬件架构深度解析:从基带原理到射频前端设计
  • 别再死记硬背MCMC了!用Python模拟一个会‘遗忘’的马尔可夫链,5分钟搞懂平稳分布
  • 番茄小说下载器终极指南:5分钟掌握全平台离线阅读与有声书生成
  • Windows与Linux文件互通革命:WinBtrfs驱动程序深度解析
  • 技术深度解析:BetterNCM Installer II - 网易云插件生态的革命性管理方案
  • 2026最新九江黄金回收白银回收铂金回收攻略,实地甄选五家优质实体店 - 诚金汇钻回收公司
  • SAP ABAP ALV表格编辑实战:手把手教你实现单元格联动更新与数据校验(含完整代码)
  • 越过“内存墙”,AI推理时代的晶圆级革命与算力路线
  • 搞懂这套公式,AI 视频不再崩!Ltx2.3-vrvb 提示词(Prompt)保姆级进阶指南
  • Calibre LVS报告解析:从错误定位到高效调试的完整指南
  • 从CAN调谐器到硅调谐器:射频前端芯片化演进与实战选型指南
  • 从IMDB电影推荐到DBLP学者分类:实战解析HAN模型在三大经典数据集上的表现