双亲委派模型的工作机制是:当类加载器收到类加载请求时,它不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载类。
JDK代码实现:
public class ClassLoader {
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
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
// 链接指定的类
resolveClass(c);
}
return c;
}
}
}
双亲委派模型的好处:
1、基于双亲委派模型规定的这种带有优先级的层次性关系,虚拟机运⾏程序时就能够避免类的重复加载。当⽗类类加载器已经加载过类时,如果再有该类的加载请求传递到子类类加载器,⼦类类加载器执⾏ loadClass ⽅法,然后委托给⽗类类加载器尝试加载该类,但是⽗类类加载器执⾏ Class<?> c= findLoadedClass(name); 检查该类是否已经被加载过这⼀阶段就会检查到该类已经被加载过,直接返回该类,⽽不会再次加载此类。
2、双亲委派模型能够避免核⼼类篡改。⼀般我们描述的核⼼类是 rt.jar、tools.jar 这些由启动类加载器加载的类,这些类库在⽇常开发中被⼴泛运⽤,如果被篡改,后果将不堪设想。假设我们⾃定义了⼀个 java.lang.Integer 类,与好处1⼀样的流程,当加载类的请求传递到启动类加载器时,启动类加载器执⾏ findLoadedClass(String) ⽅法发现 java.lang.Integer 已经被加载过,然后直接返回该类,加载该类的请求结束。虽然避免核⼼类被篡改这⼀点的原因与避免类的重复加载⼀致,但这还是能够作为双亲委派模型的好处之⼀的。
双亲委派的不足:
1、由于历史原因( ClassLoader 类在 JDK1.0 时就已经存在,⽽双亲委派模型是在 JDK1.2 之后才引⼊的),在未引⼊双亲委派模型时,⽤户⾃定义的类加载器需要继承 java.lang.ClassLoader 类并重写 loadClass() ⽅法,因为虚拟机在加载类时会调⽤ ClassLoader#loadClassInternal(String) ,⽽这个⽅法(源码如下)会调⽤⾃定义类加载重写的 loadClass() ⽅法。⽽在引⼊双亲委派模型后, ClassLoader#loadClass ⽅法实际就是双亲委派模型的实现,如果重写了此⽅法,相当于打破了双亲委派模型。为了让⽤户⾃定义的类加载器也遵从双亲委派模型, JDK新增了 findClass ⽅法,⽤于实现⾃定义的类加载逻辑。
class ClassLoader {
// This method is invoked by the virtual machine to load a class.
private Class<?> loadClassInternal(String name) throws ClassNotFoundException{
// For backward compatibility, explicitly lock on 'this' when
// the current class loader is not parallel capable.
if (parallelLockMap == null) {
synchronized (this) {
return loadClass(name);
}
} else {
return loadClass(name);
}
}
// 其余⽅法省略......
}
2、 由于双亲委派模型规定的层次性关系,导致⼦类类加载器加载的类能访问⽗类类加载器加载的类,⽽⽗类类加载器加载的类⽆法访问⼦类类加载器加载的类。为了让上层类加载器加载的类能够访问下层类加载器加载的类,或者说让⽗类类加载器委托⼦类类加载器完成加载请求,JDK 引⼊了线程上下⽂类加载器,藉由它来打破双亲委派模型的屏障。
3.、当⽤户需要程序的动态性,⽐如代码热替换、模块热部署等时,双亲委派模型就不再适⽤,类加载器会发展为更为复杂的⽹状结构。
双亲委派机制的三次破坏
1、第一次破坏发生在双亲委派机制引入之前。由于双亲委派机制在JDK1.2才被引入,而在之前就有了类加载器和抽象类ClassLoader,为了兼容这些代码,无法再用技术手段避免LoadClass()方法被子类覆盖的可能,所以,ClassLoader类中加入了一个procted的findClass方法,引导用户在定义类加载逻辑时在该方法中实现,而不是loadClass方法。在该场景中双亲委派的具体逻辑在LoadClass中,如果父类加载失败,调用自己的findClass方法完成加载。
2、双亲委派机制中父类加载器无法委托子类加载器,所以基础类无法调用用户的代码。
线程上下文加载器是定义在Thread类中的一个ClassLoader类型的私有成员变量,指向了当前线程的类加载器。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。线程上下文加载器可以让父类加载器委托子类类加载器完成加载请求(破坏双亲委派机制),所有涉及SPI(Service Provider Interface)的加载动作基本上都采用这种方式,比如JNDI,JDBC等。
3、第三次对双亲委派机制的破坏是由于用户对程序动态性的追求导致的,如:代码热替换、模式热部署等。
在动态模型OSGi中,每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就连同这个Bundle的类加载器一起换掉,来实现代码的热替换。在OSGI环境下,类加载器不再是双亲委派模型中的树状结构,而是更复杂的网状结构。
当收到类加载请求时,OSGi将按照下面的顺序进行搜索: