关于java:双亲委派模型

39次阅读

共计 5009 个字符,预计需要花费 13 分钟才能阅读完成。

前言

明天大头菜打算讲双亲委派模型,重点关注:如何毁坏双亲委派模型,你看完后,肯定会获益匪浅哈哈哈。
广告工夫:先点赞,先珍藏,转粉不转路。

问题

大家思考一下这些问题:

  1. 为什么不能定义 java.lang.Object 的 Java 文件?
  2. 在多线程的状况下,类的加载为什么不会呈现反复加载的状况?
  3. 以下代码,JVM 是怎么初始化注册 MySQL 的驱动 Driver?
        Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "root");

解答

以上这些问题,其实都和双亲委派模型无关,双亲委派模型,在面试中,是十分热的考察点。

首先,咱们得晓得什么是双亲委派模型?

简略点说,所谓的双亲委派模型,就是加载类的时候,先申请其父类加载器去加载,如果父类加载器无奈加载类,再尝试本人去加载类。如果都没加载到,就抛出异样。

当初让咱们回到第一个问题:为什么不能创立 java.lang.Object 的 Java 文件?

即便咱们曾经定义了 java.lang.Object 的 Java 文件,但其实也无奈加载,因为 java.lang.Object 曾经被启动类加载器加载了。

你可能会问:为什么 JVM 要应用双亲委派模型来加载类?

一,性能,防止反复加载;二,安全性,防止外围类被批改。

第一点,没法好说的。说说第二点安全性吧,你试想一下。假如我当初创立一个 java.lang.Object 的 Java 文件,而后在外面植入一些病毒木马,或者写一些死循环在构造方法中。对 JVM 来说,这是致命的。

接下来,简略介绍一下各种类加载器:

  • 启动类加载器:它不是一个 Java 类,是 C ++ 写的。次要负责 JDK 的外围类库,比方 rt.jar,resource.jar 等类库。启动类加载器齐全是 JVM 本人管制的,开发人员是无法访问的。
  • 扩大类加载器:是一个继承 ClassLoader 类的 Java 类,负责加载{JAVA_HOME}/jre/lib/ext/ 目录下的所有 jar 包
  • 应用程序类加载器:是一个继承 ClassLoader 类的 Java 类,负载加载 classpath 目录下的所有 jar 和 class 文件,基本上你写的类文件,都是被应用程序类加载器加载的。

能够用以下代码,打印出三个类加载器的加载的文件。

public class TestEnvironment {public static void main(String[] args) {
        // 启动类加载器
        System.out.println("1"+System.getProperty("sun.boot.class.path"));
        // 扩大类加载器
        System.out.println("2"+System.getProperty("java.ext.dirs"));
        // 利用类加载器
        System.out.println("3"+System.getProperty("java.class.path"));
    }
}

补充一下:三个类加载器的关系,不是父子关系,是组合关系。

接下来咱们看看类加载器的加载类的办法 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 {
                    // 这里通过是否有 parent 来辨别启动类加载器和其余 2 个类加载器
                    if (parent != null) {
                        // 先尝试申请父类加载器去加载类,父类加载器加载不到,再去尝试本人加载类
                        c = parent.loadClass(name, false);
                    } else {
                        // 启动类加载器加载类,实质是调用 c ++ 的办法
                        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;
        }
    }

总结一下 loadClass 办法的大略逻辑:

  1. 首先加锁,避免多线程的状况下,反复加载同一个类
  2. 当加载类的时候,先申请其父类加载器去加载类,如果父类加载器无奈加载类时,才本人尝试去加载类。

下面的源码解析,能够答复问题:在多线程的状况下,类的加载为什么不会呈现反复加载的状况?

好,目前咱们曾经解决 2 个问题了。

接下来

就要开始毁坏双亲委派模型了。首先申明哈,双亲委派模型,JVM 并没有强制要求恪守,只是说 举荐

咱们来总结一下,双亲委派模型就是子类加载器调用父类加载器去加载类。那如何来毁坏呢?能够使得父类加载器调用子类加载器去加载类,这便毁坏了双亲委派模型。

在解说 MySQL 的驱动前,先补充一个知识点:

Class.forName() 与 ClassLoader.loadClass() 两品种的加载形式的区别

Class.forName()

  • 本质是调用原生的 forName0()办法
  • 保障一个 Java 类被无效得加载到内存中;
  • 类默认会被初始化,即执行外部的动态块代码以及保障动态属性被初始化;
  • 默认会应用以后的类加载器来加载对应的类(先记住这个特点,上面会用到)

ClassLoader.loadClass()

  • 本质是启动类加载器进行加载
  • 与 Class.forName()不同,类不会被初始化,只有显式调用才会进行初始化。
  • 类会被加载到内存中
  • 提供一种灵便度,能够依据本身的需要继承 ClassLoader 类实现一个自定义的类加载器实现类的加载。

咱们持续讲一下对于 MySQL 的驱动,咱们列举 2 种状况进行比照了解:

第一种:不毁坏双亲委派模型

自定义的 Java 类

         // 1. 加载数据拜访驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 连贯到数据 "库" 下来
        Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "");

剖析一下这 2 行代码:
第一行:进行类加载,还记得下面说过 Class.forName()会应用以后的类加载器来加载对应的类。以后的类,就是用户写的 Java 类,用户写的 Java 类应用应用程序类加载器加载的。那当初问题就是应用程序类加载器是否能加载 com.mysql.jdbc.Driver 这个类,答案是能够的。因而这种形式加载类,是不会毁坏双亲委派模型的。
第二行:就是通过遍历的形式,来获取 MySQL 驱动的具体连贯。

第二种:毁坏双亲委派模型
在 JDBC4.0 当前,开始反对应用 spi 的形式来注册这个 Driver,具体做法就是在 mysql 的 jar 包中的 META-INF/services/java.sql.Driver 文件中指明以后应用的 Driver 是哪个,而后应用的时候就间接这样就能够了:

 Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "");

这和不毁坏双亲委派模型的代码有啥区别:就是少了 Class.forName(“com.mysql.jdbc.Driver”)这一行。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException { }

    static {
        try {DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {throw new RuntimeException("Can't register driver!");
        }
    }
}

因为少了 Class.forName(),因为就不会触发 Driver 的动态代码块,进而少了注册的过程。

当初,咱们剖析下看应用了这种 spi 服务的模式本来的过程是怎么的:

第一,从 META-INF/services/java.sql.Driver 文件中获取具体的实现类名“com.mysql.jdbc.Driver”
第二,加载这个类,这里必定只能用 class.forName(“com.mysql.jdbc.Driver”)来加载。

好了,问题来了,当初这个调用者是 DriverManager,加载 DriverManager 在 rt.jar 中,rt.jar 是被启动类加载器加载的。还记得下面 Class.forName()会应用以后的类加载器来加载对应的类。也就是说,启动类加载器会去加载 com.mysql.jdbc.Driver, 但真的能够加载失去吗?很显著不能够,为什么?因为 om.mysql.jdbc.Driver 必定不在 <JAVA_HOME>/lib 下,所以必定启动类加载器是无奈加载 com.mysql.jdbc.Driver 这个类。这就是双亲委派模型的局限性:父类加载器无奈加载子类加载器门路中的类。

问题咱们定位进去了,接下来该如何解决?

咱们剖析一下,列出来:

  • 一,能够必定 com.mysql.jdbc.Driver,只能由应用程序类加载器加载。
  • 二,咱们须要应用启动类加载器去获取应用程序类加载器,进而通过应用程序类加载器去加载 com.mysql.jdbc.Driver。

那么问题就变为了:如何让启动类加载器去获取应用程序类加载器?

为了解决上述的问题,咱们须要引入一个新概念:线程上下文类加载器

线程上下文类加载器(ThreadContextClassLoader)。这个类加载器能够通过 java.lang.Thread 类的 setContextClassLoaser()办法进行设置,如果创立线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范畴内都没有设置过,那么这个类加载器默认就是应用程序类加载器。

线程上下文加载器:能够让父类加载器通过调用子类加载器去加载类。

这里得留神一下:咱们之前定义的双亲委派模型是:子类加载器调用父类加载器去加载类。当初相同了,换句说,其实曾经毁坏了双亲委派模型。

如果你看到这里,置信你曾经会解答问题 3 了吧。

明天就到这里完结了!今天见!

参考资料

《深刻了解 JAVA 虚拟机》
低情商的大仙——以 JDBC 为例谈双亲委派模型的毁坏

絮叨

非常感谢你能看到这里,如果感觉文章写得不错 求关注 求点赞 求分享 (对我十分十分有用)。
如果你感觉文章有待进步,我非常期待你对我的倡议,求留言。
如果你心愿看到什么内容,我非常期待你的留言。
各位的捧场和反对,是我创作的最大能源!

正文完
 0