关于双亲委派机制,是在面试过程中会被经常问到的问题,在JVM加载类文件的过程中,需要通过类加载器进行加载,在Java中提供了很多类加载器,可以更具不同的文件进行不同的类加载。下面我们就来一起聊聊关于类加载器与双亲委派机制。
类加载器
在Java中提供了如下的四种类加载器。
- Bootstrap ClassLoader 启动类加载器
- Extention ClassLoader 标准扩展类加载器
- Application ClassLoader 应用类加载器
- Custom ClassLoader 自定义类加载器。
如图所示,一般来讲可以认为下层的类加载器继承了上层的类加载器,也就是说除了Bootstrap ClassLoader类加载器之外,其他所有的类加载器都有自己对应的父类。
双亲委派机制
所谓的双亲委派机制,其实就是指:当一个类加载器进行类加载操作的时候,他们不会直接对指定的类进行加载,而是将这个加载类的请求委托给了自己的父类去加载,当父类无法加载这个类的时候,才会将这个类指派给当前类加载器进行加载。按照这个意思可以形象的将双亲委派机制,看做一个“妈宝男”机制,什么时候都将自己的事情交给双亲来做。
根据上面的分析,我们知道了只有在父类无法加载的情况下才会有当前的类加载器进行加载操作,那么什么时候才是父类加载器无法加载呢?
对于上面提到的四种类加载器来讲,它们各自有着各自的职责。
- Bootstrap ClassLoader:其主要的职责就是加载Java的核心类库;例如rt.jar,resource.jar等等
- Extention ClassLoader:其主要职责就是加载核心类库的扩展类;例如在lib/ext路径下的相关Jar的加载。
- Application ClassLoader:其主要职责就是加载对应Java应用下的所有的Class文件
- Custom ClassLoader:主要是用户自定义的一些类加载器,可以加载一些用户指定的Class文件。
为什么要使用双亲委派机制?
说完了双亲委派机制,那么为什么Java语言要使用双亲委派机制呢?首先来讲,Java语言是一种面向对象的编程语言,也就是在我们之前介绍的时候提到的一切皆对象。而在Java语言中,离不开面向对象的三大特征。继承、封装、多态。其中继承操作我们之前提到过它是对于父类操作的扩展。也就是说在对象创建的过程中,如果没有父类对象,子类对象其实是不存在的。
也是由于这种关系,在Java的类加载其中就根据层级,以及所处的运行环境的位置来定义了四种类加载器。
首先在JVM运行的时候,需要去将底层的运行环境搭建起来,也就是在rt.jar包中会存在一些支持基础运行环境搭建的类。需要通过Bootstrap ClassLoader来进行加载,并且对于基础支持操作来讲,所有的JVM运行环境的搭建都是离不开这些基础支持的。
例如用户要去加载一个自定义的类,而这个类在我们的概念理解中它是要支持应用运行的,所以说会被一直委托到BootstrapClassLoader进行加载,这个时候BootstrapClassLoader发现这个类并不是自己需要去加载的类。就交给了ExtentionClassLoader类加载器进行加载,然后依次到ApplicationClassLoader进行加载。
这种机制的好处就是可以保证所有的类都可以被加载,并且不会出现重复加载的情况。什么意思呢?如果有一个类需要被加载,它执行到上层的类加载器的时候就会被加载,到下层类加载器中发现它被加载之后就不会再次加载,而每层的类加载器都有自己的职责,所以只完成对于自己该加载的类的加载操作。这样就不会出现,底层类加载器修改上层类加载器加载的类的操作,同时也就保证了类加载的安全性。防止核心API被改变。
相信有很多人在学习类加载机制的时候都想过自定义一个类加载器去修改别人的代码。这种方式是行不通的。
类加载器之间的关系
很多人在看到层次架构之后,就认为,类加载器之间存在父子继承的关系,其实不然。在双亲委派机制模型中只展示了一种类的层次关系,并不是体现继承关系。类加载器的拓展都是通过与父类的组合来实现的。如下,在ClassLoader类加载器中有如下一段代码,从中可以看出来,所有的继承的操作都是需要有一个父类的 ClassLoader。并且在使用继承操作的时候,必须先要有super()的父类构造。
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
如何实现双亲委派机制?
Java的类加载机制是由ClassLoader中的loadClass()方法来实现,双亲委派机制也是在这个方法中体现,方法如下。
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;
}
}
- 第一步、先根据类名进行检查,该类是否被加载了。
- 第二步、如果没有加载,则调用父类的loadClass()操作来进行加载
- 第三步、如果父类加载器为BootstrapClassLoader或者是为空,则调用自己的loadClass()方法
- 第四步、如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
总结
从上面的介绍中,我们了解了关于双亲委派机制的相关内容,并且通过源码来解释了如何实现双亲委派机制。在后续的分享中我们还会介绍关于双亲委派机制的扩展内容。以及如何实现自定义的类加载器等内容。希望大家多多关注,多多点赞支持