ClassLoader之浅谈双亲委派模型

Java的ClassLoader一直是一个很神奇的东西,很多黑科技都离不开它的存在,想要成为高级java工程师,它也基本是面试必问的,之前想要学习一直不得法,最近琢磨出一点味道了,分享给大家。

ClassLoader为何存在?

java源文件编译后都是以字节码存在.class文件中,想要使用这个class,我们必须将.class文件中的字节码翻译成内存中对应的结构,从而被jvm虚拟机使用,classloader也就是起这个翻译作用。换句话说,所有类在被jvm使用前,都要经过classloader,以面向切面编程的思想来说,这里就能做很多手脚了。

ClassLoader种类

虽说大家都是java类,但java类也是分三六九等的,不是同一阶级的java类自然不能使用同一种classloader去加载,总的来说分为四类:

  • 启动类加载器(Bootstrap ClassLoader)
    加载放在<JAVA_HOME>/lib下的或被-Xbootclasspath参数所指定的路劲中一些事先定义好的类库,比如rt.jar。注意,即使你放置一个自己的my.jar到这些路劲下也是不会被加载的。
  • 扩展类加载器(Extension ClassLoader)
    加载<JAVA_HOME>/lib/ext下的类库,或者被java.ext.dirs系统变量指定位置的类库。
  • 应用程序类加载器
    加载开发者自己定义的classpath上的类库,比如我们自己编写的java代码编译成的class文件就是由这个类加载器加载的。
  • 自定义类加载器
    有时候,应用程序类加载器不能满足我们的要求,会自己定义一些类加载器。但其实这应该也算一种应用程序类加载器。

双亲委派模型的意义

继续往下阅读之前,我们明确一个知识点:java判断一个两个对象是否相等的前提是在同一个ClassLoader加载的前提下,比较两个不同classloader加载的对象没有意义
从上面我们看到不同类型的类加载器一般只加载固定目录的类库,但是,类加载器是可以自定义的,我非要在自定义的类加载器中加载<JAVA_HOME>/lib下的类又会怎样呢?如果非要这么做的话,java基本的euqal等方法就成立了,整个java体系也就乱了,所以为了解决这个问题,双亲委派模型就推出来了。
先来一张经典图片


所谓的双亲委派模型就是在加载一个类时,先将这个类交给父级加载器加载,如果父级加载器无法加载再由自己加载
这样,我们可以保证Object类永远都是由启动类加载器加载,也就不存在equals方法不能正常使用的问题了。

如何实现双亲委派模型

既然双亲委派模型如此重要,那么我们平时自定义classloader时肯定也是要遵循这个模型的,首先我们看下默认的是怎么实现的,代码在ClassLoader类的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 {
                    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.
                    //父级加载器无法加载则自己加载,核心就是findClass方法
                    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()方法中实现了标准的双亲委派模型,如果我们不想打破这个模型,那么我们重写findClass()方法就好了。

双亲委派模型的破坏

无疑双亲委派模型是一种非常合适的类加载模型,但万事没有绝对,这个模型也有被破坏的时候。

JDK1.2之前的破坏

双亲委派模型是jdk1.2之后才被引入,而classloader是jdk1.0发布的,那么这段时间大家自定义classloader都是重写loadClass()方法,自然不存在遵守双亲委派模型的情况了。

自身缺陷导致的被破坏

事实上,双亲委派模型有一个很大的缺陷,那就是父级加载器是永远不可能加载子级加载器路径中的类的,但我们确实是有这种需求的。最经典的就是java本身会对一些操作定义一些标准的接口,比如sql的接口,log的接口等等,这些接口的具体实现由各大厂商实现,比如mysql有自己Driver实现,oracle也有自己的driver实现。java如果想做到在父级路径的类中加载这些厂商提供的类必然会出现上级类加载器加载下级类加载器路径中类的情况,这在双亲委派模型中是做不到的。
对于这种情况的具体理解大家可以看下我的这篇文章,有详细的解释:
以JDBC为例谈双亲委派模型的破坏

追求“动态化”,“热部署”导致的破坏

java为了追求热部署,热修复,推出了一套osgi规范,这套规范中ClassLoader不再遵循双亲委派模型,而是变成了一个网状结构,关于osgi规范比价复杂,目前应用也不多,不是几篇文章能讲明白的,大家有兴趣可以去看这本书 《深入理解OSGI:Equinox原理、应用与最佳实践》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章