JAVA 双亲委派模型
在上一篇博文中,我们知道了如何获得二进制的字节流,并根据获得的字节流去装载一个类。同时也了解到类加载器的存在,每个加载器对应着不同的加载目录,相互配合着,从而使整个加载过程稳定而安全。
那么他们是如何配合的呢?如果我自己写一个类,名字叫做String可以吗?
首先我们来看一张图:
图中除了最底下的那个加载器是我们没有讲到的,其余的都有说到过。其实底下那个就是我们自己实现的类加载器,用于自定义加载class
。
在虚拟机中,类加载采用的是双亲委派模型。该模型需要有一个前提条件:除了顶层的类加载器之外,其余的类加载器都应该有自己的父加载器,这里的父加载器指的不是继承,而是组合,即App ClassLoader
加载器里面应该要有Extension ClassLoader
加载器的引用(为什么用组合而不用继承,大家可以想想其中的利弊)。
基本工作过程就是:当一个类加载器收到了类加载的请求,他首先不会尝试自己去加载这个类,而是将这次的请求委派给自己的父类加载器去加载。如果父类加载器依然不能加载,则继续用父加载器的父加载器去加载。
层层如此,如果都不能加载,则最终的结果就是到达顶层——启动类加载器Bootstrap ClassLoader
。每一层的类加载器都会根据请求所要加载的类去自己应该加载的目录中搜索有没有对应的类和查看该类是否已经被加载。如果有,那么该层加载器加载并返回,如果到达了启动类加载器后还是不能加载,那么就由最初接收到类加载请求的那个类加载器进行加载。
因此,可以从图或者刚刚的工作过程看出:要加载类就先要检查是否已经加载了和是不是自己应该要加载的类,这个过程是自底向上的。那么如果上层反馈说该类没有被加载,并且我和我的父加载器都不能加载,你自己加载吧,这个过程是从上往下的。
我们来看看虚拟机是怎么实现该双亲模型的:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 先检查是否已被当前ClassLoader装载
Class c = findLoaderClass(name);
if (c === null) {
try {
if (parent != null) {
// 如果没被当前ClassLoader加载,则递归到父中装载,自底向上
c = parent.loadClass(name, false);
} else {
// 装载树已到根部,若还没找到则转至Bootstrap装载器中查找,注意,虽然Bootstrap是所有加载器的根,但它是C++实现的,不可能放到子的parent中。因此,第二层装载器(扩展类加载器Extension ClassLoader)可视为是所有加载器的根。
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 若Bootstrap装载器都无法装载,则用当前装载器。子类可以在findClass方法中调用defineClass,把从自定义位置获得的字节码转换成Class。
c = findClass(name);
}
}
if (resolve) {
resolveClass(c)
}
return c;
}
通过findLoadedClass判断是否已经加载了,如果加载了,则返回这个类,否则就判断有没有父加载器,如果有就交给父加载器加载。如果祖先都不能加载,就交给当前的加载器加载。
双亲委派模式很好地解决了各个类加载器的基础类统一问题,越基础的类由越上层的类加载器进行加载。分工与责任明确,解决部分安全问题,如自定义的加载器不能加载根加载器应该加载的类,很好的避免了恶意写入基础类。
例子:我们常用的String类位于rt.jar下。由上一博文的知识可以知道,该jar包是由Bootstrap ClassLoader进行加载的,那么如果我们自己写一个类也叫作String,那么当加载的时候,就会先检查出该类已经被加载了,所以不再允许其他的加载器重新加载,因此我们自己写的String类也就不能用了,所以我们不能自己写一个叫做String的类。
- 点赞
- 收藏
- 关注作者
评论(0)