乐趣区

双亲委派模型

本周,我的秋招正式进入到了面试阶段,不过当初来看,成果不尽人意,字节跳动一面开始的问题还比较简单,但却挂在了算法上,而面试阿里的时候的时候却感到了本人常识的深度与广度都还不够。其中一个问题便是:什么是双亲委派模型?嗯,这个背过。面试官接着又是一问,如何突破?这可没学过……

双亲委派模型

咱们晓得,java 的类加载过程是:加载 -> 验证 -> 筹备 -> 解析 -> 初始化 -> 应用 -> 卸载。

加载过程能够简略通过 ClassLoader 将.class 文件加载 jvm 中。

同时,咱们要晓得,在 Java 中 任意一个类都是由这个类自身和加载这个类的类加载器来确定这个类在 JVM 中的唯一性 。也就是你用你 A 类加载器加载的com.aa.ClassA 和你 A 类加载器加载的 com.aa.ClassA 它们是不同的,也就是用 instanceof 这种比照都是不同的。所以即便都来自于同一个 class 文件然而由不同类加载器加载的那就是两个独立的类。

什么是双亲委派

首先须要阐明的是,双亲委派这个名字是很容易让人误会的,因为并不能帮忙人了解前面的加载过程,反而让人感觉晕。双亲委派的原文是 ”parents delegate”。parents 在英文中是“父母”、“双亲”的意思,但其实表白的是“父母这一辈”的人的意思。实际上这个模型中,只是表白“父母这一辈”的 class loader 而已,所以所谓的双亲委派就是:

如果一个类加载器收到了加载某个类的申请,则该类加载器并不会去加载该类,而是把这个申请委派给父类加载器,每一个档次的类加载器都是如此,因而所有的类加载申请最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜寻范畴内无奈找到所需的类,并将该后果反馈给子类加载器,子类加载器会尝试去本人加载。

在 java 中,ClassLoader 有四种

  • BootStrapClassLoader: c++ 编写,加载外围库 java.*;
  • Extension ClassLoader: java 编写,加载扩大库 javax.*;
  • AppClassLoader: java 编写吗,加载程序目录;
  • 自定义 ClassLoader:java 编写,定制化加载;

应用双亲委派的益处

虚拟机只有在两个类的类名雷同且加载该类的加载器均雷同的状况下才断定这是一个类。若不采纳双亲委派机制,同一个类有可能被多个类加载器加载,这样该类会被辨认为两个不同的类,互相赋值时会有问题。

双亲委派机制能保障多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载后果雷同。

其次是思考到平安因素。假如通过网络传递一个名为 java.lang.Integer 的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在外围 Java API 发现这个名字的类,发现该类已被加载,并不会从新加载网络传递的过去的 java.lang.Integer,而间接返回已加载过的 Integer.class,这样便能够避免外围 API 库被随便篡改。

突破双亲委派模型

双亲委派模型不是一种强制性束缚,也就是你不这么做也不会报错怎么的,它是一种 JAVA 设计者举荐应用类加载器的形式。

再想想双亲委派是怎么加载的,给父类,那咱们自定义一个 ClassLoader 不给父类不就行了吗……过后的本人居然没想到,还是学的不够透彻。

咱们能够复写 loadClass(), 让其不向上委派

public class UnDelegationClassLoader extends ClassLoader {
  private String classpath;

  public UnDelegationClassLoader(String classpath) {super(null);
    this.classpath = classpath;
  }

  @Override
  public Class<?> loadClass(String name) throws ClassNotFoundException {Class<?> clz = findLoadedClass(name);
    if (clz != null) {return clz;}

    // jdk 目前对 "java." 结尾的包减少了权限爱护,这些包咱们依然交给 jdk 加载
    if (name.startsWith("java.")) {return ClassLoader.getSystemClassLoader().loadClass(name);
    }
    return findClass(name);
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    InputStream is = null;
    try {String classFilePath = this.classpath + name.replace(".", "/") + ".class";
      is = new FileInputStream(classFilePath);
      byte[] buf = new byte[is.available()];
      is.read(buf);
      return defineClass(name, buf, 0, buf.length);
    } catch (IOException e) {throw new ClassNotFoundException(name);
    } finally {if (is != null) {
        try {is.close();
        } catch (IOException e) {throw new IOError(e);
        }
      }
    }
  }
  
}

JDBC、Tomcat 中为什么要毁坏双亲委派模型

对本问题的具体钻研能够看——阿里面试题:JDBC、Tomcat 为什么要毁坏双亲委派模型

JDBC

JDBC 的 Driver 接口定义在 JDK 中,其实现由各个数据库的服务商来提供,比方 MySQL 驱动包。DriverManager 类中要加载各个实现了 Driver 接口的类,而后进行治理,然而 DriverManager 位于 JAVA_HOME 中 jre/lib/rt.jar 包,由 BootStrap 类加载器加载,而其 Driver 接口的实现类是位于服务商提供的 Jar 包,依据类加载机制,当被装载的类援用了另外一个类的时候,虚拟机就会应用装载第一个类的类装载器装载被援用的类。也就是说 BootStrap 类加载器还要去加载 jar 包中的 Driver 接口的实现类。咱们晓得,BootStrap 类加载器默认只负责加载 JAVA_HOME 中 jre/lib/rt.jar 里所有的 class,所以须要由子类加载器去加载 Driver 实现,这就毁坏了双亲委派模型。

这个子类加载器是通过 Thread.currentThread().getContextClassLoader() 失去的线程上下文加载器。

在 sun.misc.Launcher 初始化的时候,会获取 AppClassLoader,而后将其设置为上下文类加载器,所以 线程上下文类加载器默认状况下就是零碎加载器

Tomcat

Tomcat 的类加载器


每个 Tomcat 的 webappClassLoader 加载本人的目录下的 class 文件,不会传递给父类加载器。

事实上,tomcat 之所以造了一堆本人的 classloader,大抵是出于上面三类目标:

  • 对于各个 webapp中的 classlib,须要互相隔离,不能呈现一个利用中加载的类库会影响另一个利用的状况,而对于许多利用,须要有共享的 lib 以便不浪费资源。
  • jvm一样的安全性问题。应用独自的 classloader去装载 tomcat本身的类库,免得其余歹意或无心的毁坏;
  • 热部署。置信大家肯定为 tomcat批改文件不必重启就主动从新装载类库而惊叹吧。

小结

面完字节,感觉本人算法太弱了,得增强算法,面完阿里,感觉本人太弱了,咋这么多不会……秋招之路,任重道远。

心愿学弟学妹们对算法还是器重起来,没事的时候能够刷刷 leetcode,毕竟,如果想进大厂,手撕代码是免不了的。

对这次阿里面试的一个简略面经。

参考文章

浅谈双亲委派模型

阿里面试题:JDBC、Tomcat 为什么要毁坏双亲委派模型

版权申明

本文作者:河北工业大学梦云智开发团队 – 李宜衡

退出移动版