前言

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

问题

大家思考一下这些问题:
  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为例谈双亲委派模型的毁坏

絮叨

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