咱们常常会在面试中遇到有关类加载器的问题,而做为一名Java开发人员应该了解类加载器如何工做?双亲委派模型是什么?如何打破双亲委派?为何打破?等等。因此今天的主题就是聊一聊类加载器。html
《深刻理解Java虚拟机》这本书你们都不陌生,想必咱们大多数人了解JVM知识都是经过这本书,在该书中也详细介绍了Java类加载的全过程,包含加载、验证、准备、解析和初始化这5个阶段。java
在加载阶段,经过一个类的全限定名来获取此类的二进制字节流,就是依靠类加载器来完成。web
类加载器的一个做用就是将编译器编译生成的二进制 Class 文件加载到内存中,进而转换成虚拟机中的类。Java系统提供了三种内置的类加载器:面试
固然,上面是 Java 默认的类加载器,咱们还能够自定义类加载器,后文会分析如何自定义类加载器。spring
网上有文章分析说,类加载器遵循三个原则:委托性、可见性和惟一性原则。这三点其实都和双亲委派模型有关,双亲委派的工做过程以下:apache
当类加载器收到类的加载请求时,首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,全部的加载请求会传送到顶层的启动类加载器,只有父类加载器没法完成加载请求,才会交由子加载器去加载。后端
三个原则的具体体现是:数组
「委托性原则」 体如今当子类加载器收到类的加载请求时,会将加载请求向上委托给父类加载器。缓存
「可见性原则」 体如今容许子类加载器查看父类加载器加载的全部类,可是父类加载器不能查看子类加载器加载的类。tomcat
「惟一性原则」 体如今双亲委派整个机制保证了Java类的惟一性,假如你写了一个和JRE核心类同名的类,好比Object类,双亲委派机制能够避免自定义类覆盖核心类的行为,由于它首先会将加载类的请求,委托给ExtClassLoader去加载,ExtClassLoader再委托给BootstrapClassLoader,启动类加载器若是发现已经加载了 Object类,那么就不会加载自定义的Object类。
聊完双亲委派模型,你确定想知道它是如何实现,那么来看一下 ClassLoader 的核心方法,其中的 loadClass 方法就是实现双亲委派机制的关键,为了缩短代码篇幅和方便阅读,去掉了一些代码细节:
package java.lang;
public abstract class ClassLoader {
protected Class defineClass(byte[] b);
protected Class<?> findClass(String name);
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 1. 检查类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//2. 委托给父类加载
c = parent.loadClass(name, false);
} else {
//3. 父类不存在的,交给启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) { }
if (c == null) {
//4. 父类加载器没法完成类加载请求时,调用自身的findClass方法来完成类加载
c = findClass(name);
}
}
return c;
}
}
复制代码
如今咱们熟悉了 ClassLoader 的三个重要方法,那么若是须要自定义一个类加载器的话,直接继承 ClassLoader类,通常状况只须要重写 findClass 方法便可,本身定义加载类的路径,能够从文件系统或者网络环境。
可是,若是想打破双亲委派机制,那么还要重写 loadClass 方法,只不过,为何咱们要选择去打破它呢? 咱们常使用的 Tomcat的类加载器就打破了双亲委派机制,固然还有一些其余场景也打破了,好比涉及 SPI 的加载动做、热部署等等。
接下来来看看 Tomcat 为何打破双亲委派模型以及实现机制。
如今都流行使用 springboot 开发 web 应用,Tomcat 内嵌在 springboot 中。而在此以前,咱们会使用最原生的方式,servlet + Tomcat 的方式开发和部署 web 程序。web 应用的目录结构大体以下:
| - MyWebApp
| - WEB-INF/web.xml -- 配置文件,用来配置Servlet等
| - WEB-INF/lib/ -- 存放Web应用所需各类JAR包
| - WEB-INF/classes/ -- 存放你的应用类,好比Servlet类
| - META-INF/ -- 目录存放工程的一些信息
复制代码
一个 Tomcat 可能会部署多个这样的 web 应用,不一样的 web 应用可能会依赖同一个第三方库的不一样版本,为了保证每一个 web 应用的类库都是独立的,须要实现类隔离。而Tomcat 的自定义类加载器 WebAppClassLoader 解决了这个问题,每个 web 应用都会对应一个 WebAppClassLoader 实例,不一样的类加载器实例加载的类是不一样的,Web应用之间通各自的类加载器相互隔离。
固然 Tomcat自定义类加载器不仅解决上面的问题,WebAppClassLoader 打破了双亲委派机制,即它首先本身尝试去加载某个类,若是找不到再代理给父类加载器,其目的是优先加载Web应用定义的类。
WebappClassLoader 具体实现机制是重写了 ClassLoader 的 findClass 和 loadClass 方法。
public Class<?> findClass(String name) throws ClassNotFoundException {
...
Class<?> clazz = null;
try {
//1. 先在 Web 应用目录下查找类
clazz = findClassInternal(name);
} catch (RuntimeException e) {
throw e;
}
if (clazz == null) {
try {
//2. 若是在本地目录没有找到,交给父加载器去查找
clazz = super.findClass(name);
} catch (RuntimeException e) {
throw e;
}
}
//3. 若是父类也没找到,抛出 ClassNotFoundException
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
复制代码
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> clazz = null;
//1. 先在本地缓存查找该类是否已经加载过
clazz = findLoadedClass0(name);
if (clazz != null) {
return clazz;
}
//2. 从系统类加载器的缓存中查找是否加载过
clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
//3. 尝试用 ExtClassLoader 类加载器类加载
ClassLoader javaseLoader = getJavaseClassLoader();
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 4. 尝试在本地目录搜索 class 并加载
try {
clazz = findClass(name);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// 5. 尝试用系统类加载器 (也就是 AppClassLoader) 来加载
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException e) {
// 省略
}
}
//6. 上述过程都加载失败,抛出 ClassNotFoundException 异常
throw new ClassNotFoundException(name);
}
复制代码
从上面的代码中能够看到,Tomcat 自定义的类加载器确实打破了双亲委派机制,同时根据 loadClass 方法的核心逻辑,我也画了一张图,描述了默认状况下 Tomcat 的类加载机制。
一开始将类加载请求委托给 ExtClassLoader,而不是委托给 AppClassLoader,这样的缘由是 防止 web 应用本身的类覆盖JRE的核心类,若是 JRE 核心类中没有该类,那么才交给自定义的类加载器 WebappClassLoader 去加载。
这篇文章主要总结了类加载器的双亲委派模型、双亲委派的工做机制、以及Tomcat如何打破双亲委派,固然有一些东西分享的比较简单,好比 Tomcat 的类加载器这部分,没有说起整个 Tomcat的类加载器层次结构,没有提到 SharedClassLoader 和 CommonClassLoader 类加载器,这个等后续有时间再来分享。
同时,欢迎关注我新开的公众号,按期分享Java后端知识!