类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段!
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个 Java 虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等!
以JDK 8为例
名称 | 加载的类 | 说明 |
Bootstrap ClassLoader(启动类加载器) | JAVA_HOME/jre/lib | 无法直接访问 |
Extension ClassLoader(拓展类加载器) | JAVA_HOME/jre/lib/ext | 上级为Bootstrap,显示为null |
Application ClassLoader(应用程序类加载器) | classpath | 上级为Extension |
自定义类加载器 | 自定义 | 上级为Application |
1、启动类的加载器
可通过在控制台输入指令,使得类被启动类加器加载
2、扩展类的加载器
如果 classpath 和 JAVA_HOME/jre/lib/ext 下有同名类,加载时会使用拓展类加载器加载。当应用程序类加载器发现拓展类加载器已将该同名类加载过了,则不会再次加载。
3、双亲委派模式
双亲委派模型工作过程:
- 当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
- 当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
- 如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。
- 如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
- 如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
- 如果均加载失败,就会抛出ClassNotFoundException异常。
双亲委派模式,即调用类加载器ClassLoader 的 loadClass 方法时,查找类的规则。
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 {
// 看是否被它的上级加载器加载过了 Extension 的上级是Bootstarp,但它显示为null
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 看是否被启动类加载器加载过
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
//捕获异常,但不做任何处理
}
if (c == null) {
// 如果还是没有找到,先让拓展类加载器调用 findClass 方法去找到该类,如果还是没找到,就抛出异常
// 然后让应用类加载器去找 classpath 下找该类
long t1 = System.nanoTime();
c = findClass(name);
// 记录时间
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
4、自定义类加载器
使用场景
- 想加载非 classpath 随意路径中的类文件
- 通过接口来使用实现,希望解耦时,常用在框架设计
- 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器
步骤
- 继承 ClassLoader 父类
- 要遵从双亲委派机制,重写 findClass 方法
- 不是重写 loadClass 方法,否则不会走双亲委派机制
- 读取类文件的字节码
- 调用父类的 defineClass 方法来加载类
- 使用者调用该类加载器的 loadClass 方法
破坏双亲委派模式
- 双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即JDK1.2面世以前的“远古”时代
-
- 建议用户重写findClass()方法,在类加载器中的loadClass()方法中也会调用该方法
- 双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷导致的
-
- 如果有基础类型又要调用回用户的代码,此时也会破坏双亲委派模式
- 双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的
-
- 这里所说的“动态性”指的是一些非常“热”门的名词:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等