Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(ClassLoader)
类加载器虽然只用于实现类的加载动作,但它在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 |
可通过在控制台输入指令,使得类被启动类加器加载
如果classpath和JAVA_HOME/jre/lib/ext 下有同名类,加载时会使用拓展类加载器加载。当应用程序类加载器发现拓展类加载器已将该同名类加载过了,则不会再次加载
双亲委派模式
双亲委派模式,即调用类加载器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,但它显示为nullif (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;}
}
使用场景(什么时候需要自定义类加载器)
◆ 想加载非 classpath 随意路径中的类文件
◆ 通过接口来使用实现,希望解耦时,常用在框架设计
◆ 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器
步骤
① 继承ClassLoader父类
② 要遵从双亲委派机制,重写 findClass 方法
——不是重写loadClass方法,否则不会走双亲委派机制
③ 读取类文件的字节码
④ 调用父类的 defineClass 方法来加载类
⑤ 使用者调用该类加载器的 loadClass 方法
破坏双亲委派模式
◆ 双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即JDK1.2面世以前的“远古”时代
——建议用户重写findClass()方法,在类加载器中的loadClass()方法中也会调用该方法
◆ 双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷导致的
——如果有基础类型又要调用回用户的代码,此时也会破坏双亲委派模式
◆ 双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的
——这里所说的“动态性”指的是一些非常“热”门的名词:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等