本周,我的秋招正式进入到了面试阶段,不过当初来看,成果不尽人意,字节跳动一面开始的问题还比较简单,但却挂在了算法上,而面试阿里的时候的时候却感到了本人常识的深度与广度都还不够。其中一个问题便是:什么是双亲委派模型?嗯,这个背过。面试官接着又是一问,如何突破?这可没学过……
双亲委派模型
咱们晓得,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
中的class
和lib
,须要互相隔离,不能呈现一个利用中加载的类库会影响另一个利用的状况,而对于许多利用,须要有共享的lib以便不浪费资源。 - 与
jvm
一样的安全性问题。应用独自的classloader
去装载tomcat
本身的类库,免得其余歹意或无心的毁坏; - 热部署。置信大家肯定为
tomcat
批改文件不必重启就主动从新装载类库而惊叹吧。
小结
面完字节,感觉本人算法太弱了,得增强算法,面完阿里,感觉本人太弱了,咋这么多不会……秋招之路,任重道远。
心愿学弟学妹们对算法还是器重起来,没事的时候能够刷刷leetcode,毕竟,如果想进大厂,手撕代码是免不了的。
对这次阿里面试的一个简略面经。
参考文章
浅谈双亲委派模型
阿里面试题:JDBC、Tomcat为什么要毁坏双亲委派模型
版权申明
本文作者:河北工业大学梦云智开发团队 - 李宜衡