一. ClassLoader的等级加载机制
如某会场的入场嘉宾分为普通VIP、白银VIP、黄金VIP、钻石VIP等,则对应的接待室也会被划分为同等级别:普通VIP接待室、白银VIP接待室、黄金VIP接待室、钻石VIP接待室,不同等级的会员会被分到不同的接待室接待。
ClassLoader的等级加载机制:
任何一个会员到达任何一个接待室时,这个接待室首先检查这个会员是否已经被自己接待过,如果是则拒绝接待;如果否则向上询问这个会员是否应该在上一级更高级别的接待室被接待。接到报告的上级接待室会继续检查这个会员是否已经被接待,如果是则拒绝接待,并将处理结果反馈给下级;如果否则再向上一级接待室(如果存在)发出 这个会员是否应该在上一级更高级别的接待室被接待 的相同请求,如此往复,直到最初的接待室确认该会员从未被上一级接待过并且也不应该有他们接待时,该会员将会被正式接待。而这个机制被称为 上级委托接待机制(亦或称 双亲委托机制)。
整个JVM平台提供三种类型的ClassLoader:为接待室服务的接待室 BootstrapClassLoader,接待第三方合作单位人员的接待室ExtClassLoader及 接待会员的接待室AppClassLoader。
1. 每个类加载器都有一个父加载器
要实现自定义类加载器,不论是直接实现抽象类CloassLoader还是继承URLClassLoader,或者其他子类,父加载器都是AppClassLoader,创建的对象都必须最终调用getSystemClassLoader( ) 作为父加载器。而该方法获取的结果正是AppClassLoader。那 AppClassLoader的父加载器又是怎样?答案如下:即 ExtClassLoader. 而ExtClassLoader则无父加载器,则说明ExtClassLoader为最高级别,同时与 BootstrapClassLoader无等级关系。因为BootstrapClassLoader并未遵循ClassLoader加载规则,同时也并没有子类。
2. 父加载器不是父类
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
ExtClassLoader和AppClassLoader都位于sun.misc.Launcher类中,它们是Launcher内部类;
同时它们都继承了URLClassLoader类。
重要的方法loadClass:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先,检测是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的
loadClass c = parent.loadClass(name, false);
} else {
//父加载器为空则调用
Bootstrap Classloader c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//父加载器没有找到,则调用findclass
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//调用resolveClass()
resolveClass(c);
}
return c;
}
}
该方法的执行过程总结:
1. 执行findLoadedClass(String)去检测这个class文件是否已经加载过。
2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
二. Tomcat中的ClassLoader分析
Tomcat容器中类的加载使用到一个类:StandardClassLoader. 如果调用Tomcat中的任何一个类,如StandardContext类通过getClass( ).getClassLoader( )返回的实例并不是SandardClassLoader,而依然是AppClassLoader。这是什么原因???
StandardClassLoader只是一个代理类,并没有覆盖ClassLoader的loadClass方法,StandardClassLoader仍然沿用上级委托接待机制,所以真正的父加载器仍然是通过AppClassLoader来完成;如果Tomcat的ClassPath没有被设置,则AppClassLoader就将加载不到容器的类,此时要通过StandardClassLoader来加载。
其实真正在意的不是Tomcat容器本身由谁加载,而是web应用如何加载;一个应用在Tomcat中由一个StandardContext类表示;由StandardContext类来解释web.xml文件中配置的所有Servlet实例,加载Servlet实例的加载器是WebappClassLoader。
WebappClassLoader类覆盖了父类的loadClass方法,并使用自己的加载机制:
(1)首先WebappClassLoader检查以前是否加载过,即其缓存器resourceEntries中是否存在;
(2)若否,则检查JVM虚拟机中是否加载过,即调用ClassLoader的findLoadedClass方法;
(3)以上二者缓存中均没有,则用SystemClassLoader即AppClassLoader在当前JVM的ClassPath调用加载请求类;
(4)若没有,通过filter() 方法检查该类是否在packageTriggers定义的包名(如:webapps目录)下,如果在则通过 StandardClassLoader类加载;否则仍然由WebappClassLoader在该项目的WEB-INF/classes目录下查找对应的字节码文件,并通过ResourceEntry对象将元信息保存在缓存器resourceEntries中。
源码参考:https://blog.csdn.net/jijianshuai/article/details/77878735