Skip to content

Latest commit

 

History

History
133 lines (86 loc) · 4.83 KB

源码分析之双亲委托模型以及如何破坏双亲委托.md

File metadata and controls

133 lines (86 loc) · 4.83 KB

双亲委托模型以及如何破坏双亲委托

双亲委托模型

类加载器的双亲委派模型在JDK 1.2时期被引入,并被广泛应用于此后几乎所有的Java程序中,但它并不是一个具有强制性约束力的模型,而是Java设计者们推荐给开发者的一种类加载器实现的最佳 实践。

优点

  • Java中的类随着它的类 加载器一起具备了一种带有优先级的层次关系。
    • 例如: java.lang.Object,java.lang.String 在程序的各种类加载器环境中都能够保证是同一个类,保证安全
      • 若加载了别人写的java.lang.String,别人想干啥干啥

源码分析

下面若没特殊声明、默认都是讨论HotSpot JVM。

HotSpot的ClassLoader实现:

  • 先检查请求加载的类型是否已经被加载过
    • 若没有则调用父加载器的 loadClass()方法,
    • 若父加载器为空则默认使用启动类加载器作为父加载器。
  • 假如父类加载器加载失败, 抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载。

HotSpot ClassLoader源码实现:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 步骤1:检查请求的类是否已经加载
            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,说明父类加载器无法完成加载请求
                   
                }

                if (c == null) {
			       //步骤2:在父类加载器无法加载时, 再调用本身的findClass方法来进行类加载 
                    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;
        }
    }

在JVM实际运行时,运行机制如下:

自定义classloader->自定义classloader: findLoadedClass
自定义classloader->AppClassLoader: loadClass
AppClassLoader->AppClassLoader: findLoadedClass
AppClassLoader -> ExtClassLoader: loadClass
ExtClassLoader->ExtClassLoader: findLoadedClass
ExtClassLoader -> BootstrapClassLoader: loadClass
Note right of BootstrapClassLoader: 没有父加载器
BootstrapClassLoader -> BootstrapClassLoader : findBootstrapClassOrNull
Note right of BootstrapClassLoader: Bootstrap也失败,则调用findClass
BootstrapClassLoader -> BootstrapClassLoader : findClass
Note left of BootstrapClassLoader: 如果父类加载器抛出\nClassNotFoundException,\n说明父类加载器无法完成加载请求
BootstrapClassLoader --> ExtClassLoader: 若自己没找到,则委托孩子加载
ExtClassLoader -> ExtClassLoader : findClass
ExtClassLoader --> AppClassLoader: 若自己没找到,\n则委托孩子加载
AppClassLoader -> AppClassLoader : findClass
AppClassLoader --> 自定义classloader: 若自己没找到,\n则委托孩子加载
自定义classloader -> 自定义classloader : findClass

破坏双亲委托

双亲委托模型并不是一个强制执行的模型,是Java设计者推荐开发者使用的类加载器实现方式。

截止到目前,双亲委派模型主要出现过3次较大规模“被破坏”的情况。

1. JDK 1.2之前

双亲委派模型在JDK 1.2之后才被引入,但是类加载器的概念和抽象类 java.lang.ClassLoader则在Java的第一个版本中就已经存在。

Java设计者们引入双亲委派模型时不得不做出一些妥协,为了兼容这些已有代码,无法再以技术 手段避免loadClass()被子类覆盖的可能性,只能在JDK 1.2之后的java.lang.ClassLoader中添加一个新的 protected方法findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在 loadClass()中编写代码。

2. 基础类型调用用户代码(如SPI机制)

以JDBC为例:

String url = "jdbc:mysql:///consult?serverTimezone=UTC";
String user = "root";
String password = "root";

Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, user, password);

3. 利用OSGi,追求代码动态性

参考资料

  • 深入理解JVM 周志明 第三版