类加载器
作用:
- 用于实现类的加载动作即将字节码文件从 JVM 外部加载到内存中
- 用于确定类的唯一性
- 提供隔离特性,为中间件开发者提供便利
类加载器种类
-
Bootstrap Class Loader(启动类加载器)
最顶层的加载器,由 C++ 实现,负责加载
%JAVA_HOME%\lib
目录下的 或者是被-Xbootclasspath
参数所指定的路径存放的类库 -
Extension Class Loader(扩展类加载器)
负责加载
%JAVA_HOME%\lib\ext
目录中,或者被 java.ext.dirs 系统变量所指定的路径中所有的类库, -
Application Class Loader(应用程序类加载器)
负责加载用户类路径上所有的类库
双亲委派模型
双亲委派模型要求
除了顶层的启动类加载器之外,其余的类加载器都应有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合的关系来复用父加载器的代码
工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是吧这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈反馈自己无法完成这个请求时(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去完成加载
双亲委派模型的优点
双亲委派模型保证了 Java 程序的稳定运作,避免了类的重复加载,保证一个类在各种类加载器环境中都能保证是同一个类。
什么情况下会破坏双亲委派模型
-
自定义类加载器时,如果不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法,可以通过重写其 loadClass() 方法来破坏双亲委派模型,因为双亲委派模型的实现逻辑就在该方法中(如果父类加载失败,会自动调用自己的 findClass() 方法)
-
双亲委派解决了各个类加载器协作时基础类型的一致性问题(越基础的类越由上层的加载器进行加载)。它们总是作为被用户代码继承、调用的 API 存在,当基础类型回调用户代码时就出现了破坏双亲委派模型的情况
例如:JNDI 服务去调用 SPI
JNDI 的代码由启动类加载器完成加载,但 JDNI 存在的目的就是对资源进行查找和集中管理,即需要调用由其他厂商实现并部署在应用程序的 ClassPath 下的 JNDI SPI 代码
显然 启动类加载器是无法完成对 SPI 的加载的,Java 引用了 线程上下文类加载器 来加载所需的 SPI 服务代码,这里出现了由父加载器去请求子加载器完成类加载的行为。
SPI 接口由 JDK 提供,需要 启动类加载器 进行加载,而 SPI 中的代码是第三方实现,无法由启动类加载器进行加载,所以此时需要 启动类加载器 来委托线程上下文类加载器这个子类加载器来完成加载
-
OSGI 实现的模块化热部署,自定义了类加载器机制的实现,每一个程序模块都有一个自己的类加载器,当需要更换一个程序模块时,就把该程序模块连同类加载器一起替换掉以实现代码的热替换。在 OSGI 环境下,类加载器不再使用 双亲委派模型推荐的树状结构,而是进一步发展为更复杂的网状结构