摸个鱼的功夫,搞懂双亲委派机制

_陈哈哈 发表于 2022/03/15 09:19:24 2022/03/15
【摘要】 五分钟,搞懂双亲委派机制。八股文扛把子,岂能不会!

  大家好,我是陈哈哈,北漂儿五年~

  一路走来,随着对技术的不断探索,发现不会的也愈来愈多。相信不少朋友和我一样,日积月累才是最有效的学习方式!想起高三时同桌小姐姐的座右铭:只有沉下去,才能浮上来。共勉(juan)。

  说到双亲委派机制,首先你得搞清楚啥是ClassLoader(类加载器)

  我们知道Java是运行在JVM虚拟机中的,它是怎么运行的呢?其实,我们在IDE中编写的Java源代码在启动时,会被编译器编译成.class的字节码文件。然后由ClassLoader负责将这些class文件给加载到JVM内存中,转为Class对象再去调用或执行

  JVM预定义了三种类加载器,自上而下包括:Bootstrap ClassLoader(启动类加载器)Extension ClassLoader (拓展类加载器)Application ClassLoader(应用程序类加载器)。当然,也可以自定义多个其他的CustomClassLoader(自定义类加载器)

  在《深入理解java虚拟机》一书中,针对我们常用的Tomcat服务器,描述了Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,如下图所示:

在这里插入图片描述

  为了方便理解,本文仅基于主要的三种进行解释,其余自定义类加载器不再赘述。

  • Bootstrap ClassLoader(启动类加载器):主要负责加载核心的类库(如java.lang.*),JVM_HOME/lib目录下的jar,以及构造Extension ClassLoader 和 Application ClassLoader 这俩类加载器。具体启动类加载器加载到的路径可通过System.getProperty(“sun.boot.class.path”)查看。
  • Extension ClassLoader(拓展类加载器):主要负责加载jre/lib/ext目录下的一些扩展的jar。具体启动类加载器加载到的路径可通过System.getProperty("java.ext.dirs")查看。
  • Application ClassLoader(应用程序类加载器):主要负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器,默认就是用这个加载器。具体启动类加载器加载到的路径可通过System.getProperty("java.class.path")查看。

CustomClassLoader(其他的自定义类加载器):主要负责加载应用程序的主函数类

那么当一个xxx.class文件被加载时的流程是什么样呢?

在这里插入图片描述

  如上图所示;对于预定义的三种类加载器,首先会在Application ClassLoader中检查是否加载过,如果之前加载过那就无需再加载了,每一级的类加载器都有自己的缓存,直接从缓存中取出使用;

  如果Application ClassLoader没有加载过,那么会拿到父加载器,调用父加载器的loadClass方法。其父类同理也会先检查自己是否已经加载过,如果没有再往上。类似递归的检查过程,截至到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载

  直到Bootstrap ClassLoader,已经没有父加载器了,这时候说明该.class必须重新加载,首先考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException

  那么有同学问了,为什么要从Application ClassLoader开始加载?想要实现双亲委派,直接从Bootstrap ClassLoader 开始加载不就行了?为什么还要向上委派一次?

  原理上讲双亲委派机制是向上查找,向下加载。向上查找是因为每个加载器有一个缓存,如果向上查找的时候发现加载器里面有数据了就直接返回不需要去jar包里面查找加载了,如果没有在向上查找,如果都没有再向下加载,节省资源,感觉也算是时间换空间。

  可以在你的IDE中搜索下ClassLoader,然后打开java.lang包下的ClassLoader类。然后找到loadClass方法,如下:

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先,检查是否已经被类加载器加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 存在父加载器,递归的交由父加载器
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 直到最上面的Bootstrap类加载器
                        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.
                    c = findClass(name);
                }
            }
            return c;
    }

  按照双亲委派模型来加载类感觉好麻烦,JDK为什么要这么玩儿呢?

  • 提高安全性

  为了保护系统核心类不被篡改。如果用户编写了一个 java.lang.Object 这种核心类,功能和系统 Object 类相同,却可能植入了恶意代码。有了双亲委派模型,自定义的 Object 类是不会被加载的,JVM启动时就会通过bootstarp类加载器把rt.jar下面的Object类加载进来,而不会加载自定义的 Object 类。因此自己重写的同名类永远不会被加载。

  那如果rt.jar下的核心类被改了呢?其实也不用担心,jvm类加载流程是加载并验证,有验证那些字节码文件是否合法的程序,你修改了就不属于合法的字节码文件了。

  • 防止程序混乱

  首先明确,jvm判定两个对象同属于一个类型:同名类实例化,实例对应的同名类的加载器必须相同。

  要是每个加载器都自己加载的话,那么可能会出现多个 Object 类,导致混乱。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。