月薪过万必会的:双亲委托模型

类加载器简介

在介绍双亲委托模型以前,先介绍一下类加载器。类加载器经过一个类的全限定名来转换为描述这个类的二进制字节流。java

对于任意一个类,被同一个类加载器加载后都是惟一的,但若是被不一样加载器加载后,就不是惟一的了。即便是源于同一个Class文件、被同一个JVM加载,只要加载类的加载器不一样,那么类就不一样。微信

如何判断类是否相同,可使用Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果进行判断,也可使用instanceof关键字进行对象所属关系的判断。
下面咱们写一个不一样类加载器加载后的类,看一下对instanceof关键字运算有什么影响:框架

public class OneMoreStudy {
    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream inputStream = getClass().getResourceAsStream(fileName);
                    if (inputStream == null) {
                        return super.loadClass(name);
                    }
                    byte[] array = new byte[inputStream.available()];
                    inputStream.read(array);
                    return defineClass(name, array, 0, array.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object object = myLoader.loadClass("OneMoreStudy").newInstance();

        System.out.println("class name: " + object.getClass().getName());
        System.out.println("instanceof: " + (object instanceof OneMoreStudy));
    }
}

运行结果:ide

class name: OneMoreStudy
instanceof: false

在运行结果中,第一行能够看出这个对象确实是OneMoreStudy类实例化出来的,但在第二行中instanceof运算结果是false,说明在JVM中存在两个OneMoreStudy类,一个是由系统应用程序类加载器加载的,另外一个是由咱们自定义的类加载器加载的。虽然都是来自同一个Class文件,在同一个JVM里,可是被不一样的类加载器加载后,仍然是两个独立的类。模块化

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。ui

类加载器的划分

除了像上面例子代码中,咱们本身实现的自定义类加载器,还有3种系统提供的类加载器:spa

  1. 启动类加载器(Bootstrap ClassLoader):它负责将存放在%JAVA_HOME%\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,而且是JVM识别的类库加载到JVM内存中。它仅按照文件名识别,如rt.jar,名字不符合的类库即便放在lib目录中也不会被加载。它是由C++语言实现的,没法被Java程序直接引用。线程

  2. 扩展类加载器(Extension ClassLoader):它负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的全部类库。它由sun.misc.Launcher.ExtClassLoader实现,开发者能够直接使用扩展类加载器。设计

  3. 应用程序类加载器(Application ClassLoader):它负责加载用户类路径(ClassPath)上所指定的类库。因为它是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也称它为系统类加载器。它由sun.misc.Launcher.AppClassLoader来实现,开发者能够直接使用这个类加载器,若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。rest

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

双亲委托模型

以前提到,对于任意一个类,都须要由加载它的类加载器和这个类自己一同确立其在JVM中的惟一性。但是有这么多种的类加载器,如何保证一个类在JVM中的惟一性呢?为了解决这个问题,双亲委托模型(Parents Delegation Model)应运而生,它就是下图所展现的类加载器之间的层次关系:

除了顶层的启动类加载器外,其他的类加载器都必须有本身的父类加载器。类加载器之间的父子关系,通常不会以继承的关系来实现,而是都使用组合关系来复用父类加载器。

类加载器收到类加载的请求后,它不会首先本身去尝试加载这个类,而是把这个请求委派给父类加载器去尝试加载。每个类加载器都是如此,所以全部的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器反馈本身没法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试本身去加载。这样就保证了类在JVM中的惟一性,也保证了Java程序稳定运做。

实现双亲委派模型的代码都集中在java.lang.ClassLoader的loadClass()方法之中,以下:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先,检查该类是否已经被加载过了
        Class<?> c = findLoadedClass(name);
        //若是没有加载过,就调用父类加载器的loadClass()方法
        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) {
                //若是父类找不到,就调用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;
    }
}

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

破坏双亲委派模型

双亲委派模型并非一个强制性的约束模型,而是Java设计者们推荐给开发者们的类加载器实现方式。大部分的类加载器都遵循这个模型,但也有例外的状况,好比下面这三种状况:

重写ClassLoader的loadClass()方法

在上面例子代码中,就是重写了ClassLoader的loadClass()方法,破坏了双亲委派模型,产生了不惟一的类。因此,不提倡开发人员覆盖loadClass()方法,而应当把本身的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里若是父类加载失败,则会调用本身的findClass()方法来完成加载,这样就能够保证新写出来的类加载器是符合双亲委派模型。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

SPI(服务提供者接口)

Java提供了不少SPI(Service Provider Interface,服务提供者接口),容许第三方为这些接口提供实现,常见的SPI有JDBC、JNDI、JCE、JAXB和JBI等。

SPI的接口由Java核心库来提供,而这些SPI的实现代码则是做为Java应用所依赖的jar包被包含进类路径(ClassPath)里。SPI接口中的代码常常须要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;SPI的实现类是由系统类加载器来加载的。引导类加载器是没法找到SPI的实现类的,由于依照双亲委派模型,启动类加载器没法委派系统类加载器来加载类。

这时候就会使用线程上下文类加载器(Thread Context ClassLoader),在JVM中会把当前线程的类加载器加载不到的类交给线程上下文类加载器来加载,直接使用Thread.currentThread().getContextClassLoader()来得到,默认返回的就是应用程序类加载器,也能够经过java.lang.Thread类的setContextClassLoader()方法进行设置。

而线程上下文类加载器破坏了双亲委派模型,也就是父类加载器请求子类加载器去完成类加载的动做,但为了实现功能,这也是一种巧妙的实现方式。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

OSGi(开放服务网关协议)

OSGi(Open Service Gateway Initiative,开放服务网关协议)技术是面向Java动态化模块化系统模型,程序模块(称为Bundle)无需从新引导能够被远程安装、启动、升级和卸载。实现程序模块热部署的关键则是它自定义的类加载器机制的实现。

在OSGi中,类加载器再也不是双亲委派模型中的树状结构,而是一个较为复杂的网状结构,类加载的规则简要介绍以下:

  1. 若类属于java.*包,则将加载请求委托给父加载器
  2. 若类定义在启动委托列表(org.osgi.framework.bootdelegation)中,则将加载请求委托给父加载器
  3. 若类属于在Import-Package中定义的包,则框架经过ClassLoader依赖关系图找到导出此包的Bundle的ClassLoader,并将加载请求委托给此ClassLoader
  4. 若类资源属于在Require-Bundle中定义的Bundle,则框架经过ClassLoader依赖关系图找到此Bundle的ClassLoader,将加载请求委托给此ClassLoader
  5. Bundle搜索本身的类资源( 包括Bundle-Classpath里面定义的类路径和属于Bundle的Fragment的类资源)
  6. 若类在DynamicImport-Package中定义,则开始尝试在运行环境中寻找符合条件的Bundle

若是在通过上面一系列步骤后,仍然没有正确地加载到类资源,则会向外抛出类未发现异常。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

总结

类加载器经过一个类的全限定名来转换为描述这个类的二进制字节流,可划分为启动类加载器扩展类加载器应用程序类加载器自定义类加载器。在双亲委托模型中,将上述各类类加载器组成一系列的父子关系,子类加载器先把类加载请求委派给父类加载器去尝试加载,父类加载器没法加载时子类加载器才本身尝试加载,这样保证了类在JVM中的惟一性。不过,也不遵循双亲委托模型的状况,好比:重写ClassLoader的loadClass()方法、SPI(服务提供者接口)、OSGi(开放服务网关协议)。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

相关文章
相关标签/搜索