Tomcat 正是通过 Context 组件来加载管理 Web 应用的,所以今天我会详细分析 Tomcat 的类加载机制。

JVM 的类加载机制及异常

类加载是什么?

Java 的类加载,就是把字节码格式“.class”文件加载到 JVM 的方法区,并在 JVM 的堆区建立一个java.lang.Class对象的实例。该实例封装 Java 类相关的数据和方法。

Class 对象又是什么呢?JVM 根据这个业务类模板来创建具体业务类对象实例。

###何时用到类加载器?

反射,就是根据 class 的名字,加载 Class 对象。

ClassLoader

这个抽象类中定义了三个关键方法,findClass 和 loadClass 等,理解清楚它们的作用和关系非常重要。比如 loadClass 就实现了双亲委派机制。

类加载器层次

JVM 默认有三个类加载器。类加载器是分层次的,每个类加载器持有一个 parent 字段,指向父加载器。类加载器的工作原理是一样的,区别是它们的加载路径不同。

BootstrapClassLoader 是启动类加载器,由 C 语言实现,用来加载 JVM 启动时所需要的核心类,比如rt.jar、resources.jar等。
ExtClassLoader 是扩展类加载器,加载\jre\lib\ext目录下 JAR 包。
AppClassLoader 是系统类加载器,加载 classpath 下的类,应用程序默认用它来加载类。

自定义类加载器,用来加载自定义路径下的类。

如果你要自定义类加载器,不去继承 AppClassLoader,而是继承 ClassLoader 抽象类,再重写 findClass 和 loadClass 方法即可,Tomcat 就是通过自定义类加载器来实现自己的类加载逻辑。

双亲委托机制的实现

具体的实现逻辑就是 loadClass 方法。
子加载器不是继承父加载器,而是持有父加载器的引用。当子加载器加载一个 Java 类时,先委托父加载器去加载,父加载器在自己的加载路径中搜索 Java 类,当父加载器在自己的加载范围内找不到时,才会交还给子加载器加载,这就是双亲委托机制。

双亲委派机制的作用

双亲委托机制是为了保证一个 Java 类在 JVM 中是唯一的。假如你不小心写了一个与 JRE 核心类同名的类,比如 Object 类,双亲委托机制能保证加载的是 JRE 里的那个 Object 类,而不是你写的 Object 类。这是因为 AppClassLoader 在加载你的 Object 类时,会委托给 ExtClassLoader 去加载,而 ExtClassLoader 又会委托给 BootstrapClassLoader,BootstrapClassLoader 发现自己已经加载过了 Object 类,会直接返回,不会去加载你写的 Object 类。

Tomcat 的类加载器

Tomcat 的类加载器打破了双亲委托机制。类加载器不是直接委托给父加载器。
类加载器优先加载 Web 应用目录下的类,然后再加载其他目录下的类,这也是 Servlet 规范的推荐做法。

loadClass 方法

先查找本地、AppExtClassLoader 的缓存。

如果都没有,就让 ExtClassLoader 去加载,这一步比较关键,目的防止 Web 应用自己的类覆盖 JRE 的核心类。

如果没有,就在本地 Web 应用目录下查找并加载。

如果没有,说明不是 Web 应用自己定义的类,那么由系统类加载器去加载。

注意,Web 应用是通过Class.forName调用交给系统类加载器的,因为Class.forName的默认加载器就是系统类加载器。

如果上述加载过程全部失败,抛出 ClassNotFound 异常。