类加载-双亲委派模型

 2023-01-19
原文作者:小粥粥出击 原文地址:https://juejin.cn/post/6977975849458860063

0. 前言

尽管两个类来源于同一个 Class 文件,被同一个虚拟机加载,但是加载他们的类加载器不同时,那这两个类就不相等

1. 双亲委派模型

1.1 系统提供的3种类加载器

  • 启动类加载器(Bootstrap ClassLoader):加载 %JRE_HOME%\lib 目录下的类,例如 java.lang.*
  • 扩展类加载器(Extension ClassLoader):加载 %JRE_HOME%\lib\ext 目录下的类
  • 应用程序类加载器(Application ClassLoader):加载用户类路径(classpath)下的类

如果有需要的可以加入自定义类加载器

1.2 工作过程

一个类加载器收到类加载的请求时,会把这个请求委派给父类加载器,每个层次的类加载器都是如此,因此最终请求传送到启动类加载器,只有当父类加载器表示自己无法完成该类的加载(它的搜索范围中没有找到所需类),子加载器才会尝试自己加载

202301011609438821.png

1.3 源码分析

    // java.lang.ClassLoader
    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) {
                    // 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;
        }
    }

1.4 流程图

202301011609444802.png

1.5 作用

  • 避免类重复加载
  • 保证基础类在各环境中都是同一个类,避免自定义覆盖基础类时出现安全问题

2 破坏双亲委派模型

  • 涉及 SPI 的(JNDI、JDBC、JCE、JAXB、JBI 等)基本都是采用线程上下文类加载器(Thread Context ClassLoader)加载,也就是父类加载器通过子类加载器完成类加载,破坏了双亲委派模型
  • 代码热替换(HotSwap)、模块热部署(Hot Deployment),例如 OSGI

学自《深入理解Java虚拟机》(第二版)