标签搜索

目 录CONTENT

文章目录

双亲委派最细解析,挖一下源码吧

陈铭
2021-03-31 / 0 评论 / 0 点赞 / 133 阅读 / 1,406 字 / 正在检测是否收录...

什么是双亲委派??

双亲委派定义

首先了解一个该概念:对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性。也就是说,一个类在jvm中是唯一的。为了保证唯一,因此用到了双亲委派:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

双亲委派作用

为什么要用双亲委派模型加载class,原因很简单,保证安全性。举个例子吗,如果你自己定义了一个String,还是在java.lang下的。不走双亲委派的结果是,jvm会用AppClassLoader加载这个String,这样的话本来你先用原生的String结果成了自定义的String,很明显java的语法和基本API的规范和统一是不能保证的。所以java走的双亲委派,并且三个加载器都有对应的加载路径,先从顶级加载器找,没有往下加载,保证了class的唯一性。

不废话,挖源码走起

Lancher

这个类顾名思义,是个启动类,准确的说是JVM启动时候对应的启动类,我们需要实例化和配置的三大类加载器都在里面。看一下源码,lancher实例化时,会把app和ext加载器实例化好并进行设置,由于Bootstrap加载器是c++写的,源码这里没有体现,但是jvm启动时也是会实例化它的

    //jvm启动时就会执行这个构造方法
    public Launcher() {
        // 先创建ext类加载器
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // 并且将自己的成员变量实例化为app类加载器
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader", e);
        }

        // 将app类加载器赋值为线程类加载器,这个就是java的spi用来加载类所用的加载器,spi破坏了双亲委派机制
        Thread.currentThread().setContextClassLoader(loader);

        // Finally, install a security manager if requested
        String s = System.getProperty("java.security.manager");
        if (s != null) {
            // init FileSystem machinery before SecurityManager installation
            sun.nio.fs.DefaultFileSystemProvider.create();

            SecurityManager sm = null;
            if ("".equals(s) || "default".equals(s)) {
                sm = new SecurityManager();
            } else {
                try {
                    sm = (SecurityManager)loader.loadClass(s).newInstance();
                } catch (IllegalAccessException e) {
                } catch (InstantiationException e) {
                } catch (ClassNotFoundException e) {
                } catch (ClassCastException e) {
                }
            }
            if (sm != null) {
                System.setSecurityManager(sm);
            } else {
                throw new InternalError(
                    "Could not create SecurityManager: " + s);
            }
        }
    }

类加载器

我们常说的三大类加载器其实都在Lancher类下面,是他的静态内部类。而这些内部类都继承一个抽象类ClassLoader。我们现在看一下一个类如何加载。都想毋庸置疑,类会从App类加载器开始加载。

public Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            int i = name.lastIndexOf('.');
            if (i != -1) {
                SecurityManager sm = System.getSecurityManager();
                if (sm != null) {
                    sm.checkPackageAccess(name.substring(0, i));
                }
            }

            if (ucp.knownToNotExist(name)) {
                // The class of the given name is not found in the parent
                // class loader as well as its local URLClassPath.
                // Check if this class has already been defined dynamically;
                // if so, return the loaded class; otherwise, skip the parent
                // delegation and findClass.
                Class<?> c = findLoadedClass(name);
                if (c != null) {
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
                throw new ClassNotFoundException(name);
            }

            return (super.loadClass(name, resolve));
        }

看一下这个类加载器的方法内部做了什么。if (ucp.knownToNotExist(name)),ucp是URLClassPath类对象,这个方法是看一下当前类加载器的路径下有没有这个类,找到就返回class对象。那如果没找到会调用super.loadClass(name, resolve),也就是抽象类的方法,咱们也看一下:

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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

这个loadClass方法就是双亲委派的核心代码,首先会判断一下parent是不是null,这个parent就是classloader对象,app类加载器的parent就是ext加载器。parent不为null就会调用parent的loadClass方法,这就是往上加载。加载到了Bootstrap就会调用findBootstrapClassOrNull方法看看能不能被Bootstrap加载器加载。一轮走下去,还是没加载到class对象,那么就只能让app加载器自己来加载。

弊端??SPI??

那么,双亲委派模型有没有什么问题,其实是有的。这么说吧,我们常常加载jdbc的drive就会打破双亲委派。这是因为,一个类的方法里面加载某个类,首先会从方法所在类对应的类加载器开始加载。加载drive的类处在Bootstrap加载器下,而drive又是我们自己定义的类(第三方包),其实是在app加载器下才能加载的。这时候双亲委派就加载不了drive了,就需要打破双亲委派,这里用到了是Java的SPI机制,可以看看我的另一篇博客。

0

评论区