author
type
status
date
slug
summary
tags
category
icon
password
为什么要打破双亲委派模型?如何打破?
JDBC
JDBC4.0之后使用SPI机制自动加载数据库驱动,JDBC的SPI接口(如
DriverManager
、Driver
)由启动类加载器加载,而具体的SPI实现即不同厂商数据库驱动(如MySQL的com.mysql.cj.jdbc.Driver
)位于应用类路径需由应用类加载器加载。但根据双亲委派的一般性规则,父类加载器无法委派子类加载器,若严格遵守该规则,DriverManager
将无法加载第三方驱动实现。为解决此类问题,引入了线程上下文类加载器(ContextClassLoader),父加载器可以请求子类加载器去完成类加载的动作,具体做法为在
ServiceLoader.load
方法中通过Thread.currentThread().getContextClassLoader()
获取上下文类加载器,来加载对应的数据库驱动。这种行为可以看作双亲委派模型“被破坏”。Tomcat
Tomcat需同时运行多个Web应用,每个应用可能依赖不同版本的库,若遵循双亲委派,所有Web应用共享同一类加载器,会导致类冲突。Tomcat通过为每个Web应用创建独立的WebappClassLoader,优先加载自身目录下的类,实现类隔离和版本共存。具体做法为自定义类加载器WebappClassLoader,重写loadClass方法,调整加载顺序,优先自己加载,核心类委派父类加载器。
为什么双亲委派从下往上,不能从上往下?
首先双亲委派是自下往上委派,自上往下去加载类。
双亲委派自下而上去委派的设计,本质是通过优先信任父加载器来确保核心类库的纯净性,同时通过层次化隔离避免类的重复加载和冲突。若采用自上而下委派的顺序,会破坏这种信任链,导致核心类被篡改的风险,增加类管理的复杂性。
关于你使用全局缓存池的想法?
双亲委派模型的设计初衷并非单纯为了缓存优化,而是为了确保类加载的安全性、唯一性和隔离性(沙箱安全),缓存是类加载器的实现细节,不是模型的设计目的。
首先,类加载器之间的命名空间是隔离的
每个类加载器都有自己的命名空间。类是由全限定名 + 加载它的类加载器唯一决定的。同一个类名,在不同的类加载器中加载,是不同的类(Class 对象)。
如果把所有类扔进一个“全局缓存池”,那么:
- 不同类加载器加载同名类可能会冲突或被篡改
- 无法实现应用级别的类隔离,比如 Tomcat 每个 WebApp 的类隔离机制就依赖于类加载器分层设计
其次,安全性问题
假如你能从用户代码中加载一个
java.lang.String
类来替换核心类,那么 JVM 的安全机制将被完全破坏。双亲委派机制就是为了避免“用户代码优先”污染核心类库。统一缓存池将使这种防护机制失效。
缓存已经存在,只是范围不是全局共享,
实际上,每个类加载器内部都维护了自己的缓存池(比如
loadedClasses
这样的数据结构),一旦某个类加载器加载过某个类,它是不会重复加载的。而双亲委派中,父类加载器如果已经加载过,也只会查一次缓存返回,不会重复加载或递归查找。
另外委派模型实际性能损耗极低,类加载本就不是性能瓶颈,单纯从缓存角度优化,为了微乎其微的性能提升而损失基础的安全性,可能破坏这些机制,导致更严重的问题。所谓“浪费时间”的双亲委派。
- 作者:Sean Liu
- 链接:http://liusx.top/article/1f3749a0-584b-8059-b3b9-cde714f91dbb
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章