双亲委派模型(Parents Delegation Model)

工作过程

  • 在《深入理解Java虚拟机》中这样说到:

如果一个类加载器收到了类加载的请求,它首先不会在即尝试加载这个类,而是把这个请求委派给“父类”加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动来类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己完成加载。

类加载器的双亲委派模型

在这里插入图片描述

  • 如上图所示,各种类加载器之间的关系称之为类加载器的“双亲委派模型”,要求除了顶层的启动类加载器之外,其余的类都应该有自己的父类加载器,不过这里的父类加载器指的并不是继承关系,而通常是一种组合关系来实现复用父加载器的代码。

各层次类加载器的加载范围

  • (1)BootstrapClasLoader,启动类加载器负责加载<JAVA_HOME>\jre\lib目录下的类库,jar包文件,使用默认jdk安装的话也就是这个路径C:\Program Files\Java\jdk1.8.0_281\jre\lib

  • (2)ExtClassLoader,扩展类加载器负责加载<JAVA_HOME>\jre\lib\ext目录下的类库,jar包文件

  • (3)AppClassLoader,系统类加载器或应用程序类加载器负责加载整个用户类路径(ClassPath)下的所有类库

  • 获取BootstrapClassLoader,启动类加载器的加载范围

 		System.out.println("BoostrapClassLoader加载的类路径:");
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (URL url:urLs){
            System.out.println(url.toExternalForm());
        }

执行结果:

BoostrapClassLoader加载的类路径:
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/classes
  • 获取ExtClassLoader的加载范围,称为应用程序类加载器或者是系统类加载器
    	System.out.println("扩展类加载器可以加载的类的路径:");
        URLClassLoader extClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader().getParent();
        URL[] extUrls = extClassLoader.getURLs();
        for (URL path:extUrls){
            System.out.println(path);
        }

执行结果:

file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/jaccess.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/jfxrt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/localedata.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/nashorn.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/sunec.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files/Java/jdk1.8.0_281/jre/lib/ext/zipfs.jar
  • 获取AppClassLoader的加载范围
 		URLClassLoader systemClassLoader = (URLClassLoader)this.getClass().getClassLoader();
        URL[] appUrls = systemClassLoader.getURLs();
        for (URL path:appUrls){
            System.out.println(path);
        }

各个类加载器之间的关系

  • BootStrap底层是C++代码实现的因此无法通过Java代码来获取它,获取的返回值是null就代表是启动类加载器
  • 从Java类继承关系上来看,AppClassLoaderExtClassLoader属于同级关系,都继承性URLClassLoader类,间接继承于ClassLoder这个抽象类,并且都是sun.misc.Launcher类中的内部类,sun.misc.Launcher该类是Java虚拟机的一个入口应用。继承关系图如下。
  • 但是从优先级关系上来说是属于下面这种结构,越上面的优先级越高,从下往上一层层委托,当上层加载器无法完成时,才会交付给下层加载器来进行加载。
    在这里插入图片描述

双亲委派模型的源码实现

  • 该模型的源码实现很简单,在java.lang.ClassLoader类中的loadClass()方法中。
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) { //未加载
                long t0 = System.nanoTime();
                try {
                    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方法来进行类的加载。
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    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(c);
            }
            return c;
        }
    }

整个过程就是先检查请求加载的类型是否已经加载过,如没有则调用父类加载器的loadClass方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,抛出ClassNotFoundException异常,这时才会调用自己的findClass方法尝试加载。

  • ClassLoader中的方法说明
  • getParent() 返回该类加载器的超类加载器
  • loadClass(String,name) 加载名称为name的类,返回结果为Java.lang.Class的实例
  • findClass(String name)查找名称为name的类,返回结果为Java.lang.Class的实例
  • findLoadedClass(String name)查找名称为name的已经被加载过来的了,返回结果为Java.lang.Class的实例

案例说明

  • 在自己的java项目下也就是用户路径下(ClassPath)创建一个java.lang.String类,即和jdk中的String类同包名,使用类加载器进行加载看使用的是系统类加载器还是启动类加载器。我们知道jdk中的String类是由启动类加载器进行加载的,但是我们自己创建了一个和String全限定名相同的String类,那么JVM会采用哪个类加载器进行加载呢?
package java.lang;
/**
 * 创建一个和核心API全限定名相同的类,来测试类加载器加载的时候是哪一个
 */
public class String {

    //在类加载的第三个阶段,负责静态代码块的显示赋值
    static{
        System.out.println("我是自定义的String类的静态代码块");
    }
}

结构如下:在这里插入图片描述
之后我们进行测试:获取器对象的类加载器

public class TestString {
    public static void main(String[] args) {
        java.lang.String str = new java.lang.String();
        System.out.println(String.class.getClassLoader());
        System.out.println("hello");
    }
}
//结果
//null
//hello

结果我们发现,并没不是系统类加载器完成String的加载,而是启动类加载器完成的核心类String的加载,
过程是这样的:
当我们自定义的类java.lang.String类进行类加载时,会从下向上一次委托,首先是系统类加载器-》扩展类加载器-》启动类加载器,当全部类委托到启动类加载器时,BootstrapClassLoader根据自己的加载类来进行匹配,发现java.lang.String属于我的加载返回,所以进行加载。那么该类的加载就不会再向下传递,当BootstrapClassLoader发现委托过来的类不属于它的加载范围时就会向下传递给扩展类加载器,再由扩展类加载器进行搜索看是否为它的加载范围,依次执行这个过程。
避免了重复加载和对java核心代码的篡改。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值