2024-10-26  阅读(73)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/7277360746

回答

双亲委派模型是 Java 类加载机制中的一个核心概念,在这个模型中,当一个类加载器尝试加载某个类时,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器无法完成这个加载请求时,子类加载器才会尝试自己去加载这个类。

这个模型有两个优点:

  1. 避免类的重复加载:当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
  2. 保证 Java 核心库的安全:Java 的核心 API 都是通过 Bootstrap ClassLoader 进行加载的,如果别人通过自定义的方式创建了一个同路径伪造的不安全的 “Java 核心”,例如:java.lang.Integer,类加载器通过向上委托,两个 Integer,那么最终被加载的应该是 Java 核心的Integer类,而并非我们自定义的,这样就避免了我们恶意篡改核心包的风险。

详解

理论

Java 提供了3 个类加载器:

  • 启动类加载器(Bootstrap ClassLoader):负责加载 Java 的核心类库,它并不是 Java 类,是 JVM 的一部分,用 C++ 实现的。它通常加载存放在 <JAVA_HOME>/jre/lib 目录下的或者被 -Xbootclasspath 指定的路径中的并且文件名是被虚拟机识别的文件。
  • 扩展类加载器(Extension ClassLoader):由 Java 实现,是 sun.misc.Launcher$ExtClassLoader 的实例。负责加载<JAVA_HOME>\lib\ext目录中或被 java.ext.dirs 系统变量所指定的路径的类库,这些类库被视为标准核心库的扩展。
  • 应用程序类加载器(Application ClassLoader):由 Java 实现,是 sun.misc.Launcher$AppClassLoader 的实例。负责加载环境变量 classpath 或者系统属性 java.class.path 指定路径下的类库。

还有一个用户自定义类加载器,我们可以通过继承 java.lang.ClassLoader 的方式来创建自己的类加载。

他们之间的关系如下:

那双亲委派模型在这个图的执行流程是如何的呢?

  1. 当加载一个类时,会先从应用程序类加载器的缓存里查找相应的类,如果能找到就返回对象,如果找不到就委托给扩展类加载器;
  2. 在扩展加载器缓存中查找相应的类,如果能找到就返回对象,如果找不到就委托给启动类加载器;
  3. 在启动类加载器中查询相应的类,如果找到就返回对象,如果找不到就继续下面流程;
  4. 在扩展加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就继续下面流程;
  5. 在应用程序类加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就返回 ClassNotFound 异常。

加载流程如下:

实现

双亲委派模型对于保证Java程序的稳定运作非常重要,但它的实现并不复杂。我们需要注意的是,在双亲委派模型中,类加载器之间的父子关系不是以继承的关系来实现,而是都使用组合关系来复用父加载器的代码的

每个类加载器(除了引导类加载器)在实例化时都会被分配一个父类加载器的引用 parent,这个 parent 持有对父类加载器的引用。当一个类加载器需要加载一个类时,它会首先调用其父加载器的loadClass方法尝试加载该类,这是双亲委派模型的体现。只有在父加载器无法加载该类时,子加载器才会尝试自己加载该类,该逻辑体现在Java.lang.ClassLoaderloadClass() 方法之中:

    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 {
                        // 父加载器为空,则为启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 如果类仍未被加载,调用自己的 findClass()来加载这个类
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文