图解类加载器和双亲委派机制,一看就懂
听说微信搜索《Java 鱼仔》会变更强哦!
本文收录于JavaStarter ,里面有我完整的 Java 系列文章,学习或面试都可以看看哦
(一)概述
我们都知道 Java 代码会被编译成 class 文件,在 class 文件中描述了该类的各种信息,class 类最终需要被加载到虚拟机中才能运行和使用。
虚拟机把 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成虚拟机可以直接使用的 Java 类型,这就是虚拟机的类加载机制。
(二)类加载的过程
一个类从被加载到卸载出内存,一共包含下面七个阶段:
加载、验证、准备、解析、初始化、使用、卸载
加载的来源有以下部分:
1、本地磁盘
2、网络下载的.class 文件
3、war,jar 下加载.class 文件
4、从专门的数据库中读取.class 文件(少见)
5、将 java 源文件动态编译成 class 文件,典型的就是动态代理,通过运行时生成 class 文件
加载的过程是通过类加载器实现的。有关类加载的其他过程我会在下一章中介绍。
(三)类加载器的分类
类加载器分为系统级别和用户级别:
系统级别的类加载器有:
1、启动类加载器(底层使用 C++实现)
2、扩展类加载器(底层使用 java 实现,是 ClassLoader 的子类)
3、应用程序类加载器(底层使用 java 实现,是 ClassLoader 的子类)
用户级别的类加载器我们统一称为自定义类加载器。
3.1 启动类加载器
首先我们来看看启动类加载器加载了哪些类,启动类加载器负责加载 sun.boot.class.path:
通过上面的代码我们可以获取到启动类加载器所加载的类:
3.2 拓展类加载器
扩展类加载器加载负责加载 java.ext.dirs,我们同样写一段代码去加载它:
可以看到,除了加载了 JDK 目录下的 ext 外,还加载了 Sun 目录下的 ext
3.3 应用程序类加载器
最后是应用类加载器,它负责加载 java.class.path:
它负责加载工程目录下 classpath 下的 class 以及 jar 包。
(四)双亲委派模型
所谓双亲委派模型,就是指一个类接收到类加载请求后,会把这个请求依次传递给父类加载器(如果还有的话),如果顶层的父类加载器可以加载,就成功返回,如果无法加载,再依次给子加载器去加载。
我们先通过代码来看一下类加载器的层级结构:
编写一个类,依次输出这个类的类加载器,父类加载器,父类的父类加载器
可以看到首先是应用程序类加载器,它的父类是扩展类加载器,扩展类加载器的父类输出了一个 null,这个 null 会去调用启动类加载器。如果你不信,我们看源码:ClassLoader 类
接着从父类加载器往下调用 findClass,如果可以加载,就直接返回 class,如果不能加载,就依次向下。如果到了自定义加载器还是无法被加载,就会抛出 ClassNotFound 异常。
我画了一个流程图来展示双亲委派模型的全过程:
双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。
(五)破坏双亲委派
双亲委派模型并不是绝对的,spi 机制就可以打破双亲委派模型。
首先我们需要了解什么是 spi,spi(Service Provider Interface)是一种服务发现机制,Java 在核心库中定义了许多接口,并且针对这些接口给出调用逻辑,但是并未给出具体的实现。开发者要做的就是定制一个实现类,在 META-INF/services 中注册实现类信息,以供核心类库使用。最典型的就是 JDBC。
Java 提供了一个 Driver 接口用于驱动各个厂商的数据库连接,Driver 类位于 JAVA_HOME 中 jre/lib/rt.jar 中,应该由 Bootstrap 类加载器进行加载。根据类加载机制,当被加载的类引用了另外一个类的时候,虚拟机就会使用加载该类的类加载器加载被引用的类,因此如果其他数据库厂商定制了 Driver 的实现类之后,按理说也得把这个实现类放到启动类加载器加载的目录下,这显然是很不合理的。
于是 Java 提供了 spi 机制,即使 Driver 由启动类加载器去加载,但是他可以让线程上下文加载器(Thread Context ClassLoader)去请求子类加载器去完成加载,默认是应用程序类加载器。但是这确实破坏了类加载机制。
版权声明: 本文为 InfoQ 作者【Java鱼仔】的原创文章。
原文链接:【http://xie.infoq.cn/article/1927dfb815f10c5caed3b5c8b】。文章转载请联系作者。
评论