双亲委派机制
- 一、JVM体系结构
- 二、双亲委派机制的含义
- 三、双亲委派机制的源代码
- 四、双亲委派机制的意义
- 五、示例代码
一、JVM体系结构
我们先在这里放一张 JVM 的体系架构图,方便我们有个总体认知。
在了解JVM的双亲委派机制之前,你不得不需要知道的几个名字:本文我们只讲上图中里的类加载子系统下的三个阶段之一(Loading,加载阶段)有关的内容,即下图中用红色线圈起来的几个名词。
引导类加载器(Bootstrap ClassLoader);
扩展类加载器(Extension ClassLoader);
应用类加载器(Application ClassLoader)。
有关其详细概念请移步至:【JVM】java的jvm类加载器和类加载子系统之JVM类加载器和类加载子系统里的加载阶段,在这里就不多说了。
二、双亲委派机制的含义
当一个类加载器收到了类加载的请求的时候,它不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
Java中提供如下四种类型的加载器,每一种加载器都有指定的加载对象,具体如下:
- Bootstrap ClassLoader(引导类加载器) :主要负责加载Java核心类库,%JAVA_HOME%/jre/lib/目录下,resources.jar或者sun.boot.class.path路径下的内容等。
- Extention ClassLoader(扩展类加载器):主要负责从 java.ext.dirs 系统属性所指定的目录中加载类库,或从JDK的安装目录的**/jre/lib/ext/**子目录(扩展目录)下加载的类库。
- Application ClassLoader(应用程序类加载器) :主要负责加载当前应用的classpath下的所有类。
- User-Defined ClassLoader(用户自定义类加载器) : 用户自定义的类加载器,可加载指定路径的class文件。
注意:这里存在的加载器之间的层级关系并不是以继承的方式存在的,而是以组合的方式处理的。
这四种类加载器存在如下关系,当进行类加载的时候,虽然用户自定义类不会由 Bootstrap ClassLoader 或是 Extension ClassLoader 加载(由类加载器的加载范围决定),但是代码实现还是会一直委托到 Bootstrap ClassLoader, 上层无法加载,再由下层是否可以加载,如果都无法加载,就会触发 findclass(),抛出 classNotFoundException。
三、双亲委派机制的源代码
打开IDEA等代码开发工具,搜索 ClassLoader 并进入类中,找到 loadClass() 方法,源代码如下:
/**
* Loads the class with the specified binary name.
* This method searches for classes in the same manner as the loadClass(String, boolean) method.
* It is invoked by the Java virtual machine to resolve class references.
* Invoking this method is equivalent to invoking #loadClass(String, boolean) loadClass(name,false).
*
* @param: name The binary name of the class
* @return: The resulting Class object
* @throws: ClassNotFoundException If the class was not found
*/
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
/**
* Loads the class with the specified binary name.
* The default implementation of this method searches for classes in the following order:
*
* 1. Invoke #findLoadedClass(String) to check if the class
* has already been loaded.
* 2. Invoke the #loadClass(String) loadClass method
* on the parent class loader. If the parent is null the class
* loader built-in to the virtual machine is used, instead.
* 3. Invoke the #findClass(String) method to find the
* class.
*
* If the class was found using the above steps, and the
* resolve flag is true, this method will then invoke the #resolveClass(Class) method on the resulting Class object.
*
* Subclasses of ClassLoader are encouraged to override
* #findClass(String), rather than this method.
*
* Unless overridden, this method synchronizes on the result of
* #getClassLoadingLock getClassLoadingLock method
* during the entire class loading process.
*
* @param: name The binary name of the class
* @param: resolve If true then resolve the class
* @return: The resulting Class object
* @throws: ClassNotFoundException If the class could not be found
*/
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;
}
}
其实这段代码已经很好的解释了双亲委派机制,为了大家更容易理解,我做了一张图来描述一下上面这段代码的流程:
从上图中我们就更容易理解了,当一个 Hello.class 这样的文件要被加载时。不考虑我们自定义类加载器,首先会在 AppClassLoader 中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的 loadClass() 方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达 Bootstrap ClassLoader 之前,都是在检查是否加载过,并不会选择自己去加载。直到 Bootstrap ClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出 ClassNotFoundException。那么有人就有下面这种疑问了?
为什么为有这样的设计?下面我们再来说下这样设计的意义就明白了这样做的好处了。
四、双亲委派机制的意义
这种设计有个好处是:
- 第一:避免类的重复加载。
- 第二:保护程序的安全,防止核心API被随意篡改。
如果有人想替换系统级别的类,比如:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap ClassLoader 加载过了(因为当一个类需要加载的时候,最先去尝试加载的就是 Bootstrap ClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
- 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
- 父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常。
五、示例代码
现在有一个例子,在我们自定义的 String 类中(包名是 java.lang包下创建),写一个main方法,执行它,如下:
package java.lang;
public class String {
static {
System.out.println("这是自定义的String类!");
}
public static void main(String[] args) {
String customClass = new String();
System.out.println(customClass);
}
}
执行后的结果如下:
可以看到,报错在java.lang.String中找不到类方法。因为String类是启动类加载器创建的,不是我们自定义的String类,故没有main方法。
完结!