探秘JVM双亲委派机制:一场深度的类加载之旅

182 阅读8分钟

前言

作为一个 Java 开发人员, Java 的 ClassNotFund 异常你在开发中一定遇到过。 JVM 为什么会抛出 ClassNotFund 的异常? 在抛出 ClassNotFund 的异常时, JVM 的类加载器都做了哪些工作呢?

我们知道 Java 程序在启动过程中,首先需要经过类加载过程。不知道你有没有考虑过这样一个问题? 当我们的程序中既有子类又有父类的时候,是应该先加载父类呢? 还是应该先加载子类? 如果先加载父类,就存在加载父类过程中又出现父类还有其对应父类的情况。那么这种情况下 JVM 该如何处理呢?

从刚才说到的这个问题就能看出, Java 类加载并非单纯的按照类逐个加载。那么面对错综复杂的类关系,JVM是如何保障类加载的有序性和安全性呢? 答案就是是 JVM 的双亲委派机制。接下来我就和你聊一下到底什么是双亲委派机制。

什么是类加载器

在聊双亲委派机制之前,先需要简单回顾一下 JVM 类加载器的知识。因为 JVM 最终是通过类加载器来完成类加载的,而类加载器在类加载过程中正是依靠双亲委派机制来完成类加载的。

图片

我们知道 JVM 是通过类加载器将 Class 文件加载到 JVM 中运行的, 但是 JVM 的类加载器到底有哪些呢? JVM 根据类的重要性不同为我们提供了三种。分别是启动类加载器扩展类加载器应用程序类加载器。除此之外,我们还可以自定义类加载器。

图片

知道了类加载器的分类后, 接下来我们学一下三种类加载器的职责。只有弄清楚了各种类加载器的职责,才能为我们后续进一步的学习双亲委派机制打下基础。

启动类加载器

首先我们要讲的是启动类加载器,启动类加载器负责加载 Java_HOME 目录下的 Lib 子目录中的类库,是 JDK 的核心类库。同时我们也可以通过 -Xbootclasspath 参数指定路径中被虚拟机认可的类库。

图片

扩展类加载器

接着我们要说的是扩展类加载器,扩展类加载器负责加载 Java_HOME 的 Lib 子目录下的 ext 子目录中的类库,也就是加载 JDK 的扩展类库。同时我们也可以通过 java.ext.dirs 系统参数变量加载指定路径中的类库。

图片

应用程序类加载器

最后我们看一下应用程序类加载器, 它负责加载用户路径下classpath下的类库。

图片

另外除了上面介绍 JVM 默认的三种类加载器外,我们也可以通过继承 java.lang.ClassLoader 根据不同的需求实现自定义的类加载器。

从类加载器分类上,我们可以看到 JVM 为不同的类库提供了不同的类加载器。但是在具体的开发过程中,我们自定义的类又免不了去继承 JDK 中的类,或者实现 JDK 中的接口。这样的话就构成了复杂的类和接口之间的依赖关系。

我们也就不能简单的通过先加载完启动类加载器中的类库,再加载扩展器类中的类库,最后再加载应用程序类加载器中的类库来解决类加载问题。那么到底如何做才能保障在复杂的类依赖关系中,类加载的顺序性和安全性呢? 这就是我们今天要聊的核心话题了 JVM 的双亲委派机制。

双亲委派机制

双亲委派机制的过程是什么样子呢? 双亲委派分为向上委派和向下委派,我们逐一讲解一下。

图片

向上委派

我们先看一下向上委派机制。向上委派机制是指,一个类在收到类加载请求后自己不会尝试加载这个类,而是把类加载请求向上委派给它的父类去完成。父类在接收到类加载请求后, 又会将它委派给自己的父类。依此类推,这样的话所有的类加载请求都向上委派到启动类加载器中。讲到这相信你也清楚了向上委派机制具体是怎么一回事了,这也是双亲委派机制, 为什么能够保证类加载的顺序性。

图片

向上委派机制的过程是什么样的呢? 它的过程可以分为三步,首先我们将自定义类加载器挂载到应用程序类加载器,接着应用程序类加载器将类加载请求委托给扩展类加载器,第三步是扩展类加载器将类加载请求委托给启动类加载器,这样依次向上委派。

图片

向下委派

接下来我们看一下向下委派机制,向下委派机制怎么理解呢? 当父类加载器在接收到类加请求后,发现自己也无法加载这个类。出现这种情况,通常原因是这个类的 Class 文件在父类的类加载路径中不存在。这个时候父类就会将这个信息反馈给子类,并向下委派子类加载器加载这个类,直到它被成功加载。这时如果仍然找不到这个类,那么JVM就会抛出 ClassNotFound 异常。

图片

了解了向下委派概念后,让我们看一下向下委派的类加载过程。向下委派类加载过程一共分为四步,首先是启动类加载器在加载路径下查找并加载 Class 文件;如果没有找到目标 Class 文件, 就交给扩展类加载器,然后扩展类加载器接着在他的加载路径下查找并加载Class文件;如果还没有找到目标Class文件,就交由应用程序类加载器加载。

接下来看第三步,应用程序类加载器在他的加载路径下查找并加载 Class 文件。如果仍然没有找到 Class 文件,就交由自定义加载器加载。第四步是在自定义类加载器中查找并加载用户指定目标下的 Class 文件。如果最终在自定义类加载路径下仍然没有找到目标 Class 文件,在 JVM会 拋出 ClassNotFound 的异常,类加载过程失败, JVM 也会启动失败。

小结

前面就是我们 JVM 中的双亲委派机制了,从双亲委派机制的加载原类可以看出,JVM的类加载器并不是按照我们想当然理解的从上向下加载的,而是通过双亲委派机制来实现的。

双亲委派机制中从下向上的委派过程,保障了先加载JVM的核心类,再加载应用程序的类,有效防止了因为应用程序中某个类存在安全问题而导致JVM变得不安全的问题。而从上向下的委派过程,则保证了需要加载的类都得到加载。如果从上向下加载过程执行完仍然没有找到需要加载的类,那么 JVM 就会抛出 ClassNotFound 的异常,这时JVM会启动失败。

从双亲委派机制我们可以看出双亲委派机制的核心是保障类的唯一性和安全性。这也充分的说明了双亲委派机制的重要性。举个例子,比如说, 在加载 rt.jar 包中的 java.lang.object 类时,无论是哪个类加载器加载这个类最终都会将类加载请求委托给启动类加载器。这样就保证了类加载的唯一性。如果在 JVM 中存在包名和类名相同的两个类, 那么这个类将无法被加载, JVM 也无法完成类加载的流程。

总结

我们在讲解 JVM 双亲委派机制之前,首先讲解了 JVM 的类加载器分类,具体包括启动类加载器、扩展类加载器和应用程序类加载器。但是我们发现在有着复杂的类继整和依赖的情况下,通过简单的从向上的顺序方式加载是有问题的。

这也是为什么有双亲委派机制的原因,接下来我们讲了双亲委派机制的核心流程,分别是向上委派和向下委派。JVM通过向上委派和向下委派结合的方式有效的解决了类加载来有序性和安全性的问题。通过这次的学习,相信大家对JVM双亲委派机制已经有了清晰的认知和联系。

图片