回答
双亲委派模型是 Java 类加载机制中的一个核心概念,在这个模型中,当一个类加载器尝试加载某个类时,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器无法完成这个加载请求时,子类加载器才会尝试自己去加载这个类。
这个模型有两个优点:
- 避免类的重复加载:当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
- 保证 Java 核心库的安全:Java 的核心 API 都是通过
Bootstrap ClassLoader
进行加载的,如果别人通过自定义的方式创建了一个同路径伪造的不安全的 “Java 核心”,例如:java.lang.Integer
,类加载器通过向上委托,两个 Integer,那么最终被加载的应该是 Java 核心的Integer类,而并非我们自定义的,这样就避免了我们恶意篡改核心包的风险。
详解
理论
Java 提供了3 个类加载器:
- 启动类加载器(Bootstrap ClassLoader):负责加载 Java 的核心类库,它并不是 Java 类,是 JVM 的一部分,用 C++ 实现的。它通常加载存放在
<JAVA_HOME>/jre/lib
目录下的或者被-Xbootclasspath
指定的路径中的并且文件名是被虚拟机识别的文件。 - 扩展类加载器(Extension ClassLoader):由 Java 实现,是
sun.misc.Launcher$ExtClassLoader
的实例。负责加载<JAVA_HOME>\lib\ext
目录中或被java.ext.dirs
系统变量所指定的路径的类库,这些类库被视为标准核心库的扩展。 - 应用程序类加载器(Application ClassLoader):由 Java 实现,是
sun.misc.Launcher$AppClassLoader
的实例。负责加载环境变量classpath
或者系统属性java.class.path
指定路径下的类库。
还有一个用户自定义类加载器,我们可以通过继承 java.lang.ClassLoader
的方式来创建自己的类加载。
他们之间的关系如下:
那双亲委派模型在这个图的执行流程是如何的呢?
- 当加载一个类时,会先从应用程序类加载器的缓存里查找相应的类,如果能找到就返回对象,如果找不到就委托给扩展类加载器;
- 在扩展加载器缓存中查找相应的类,如果能找到就返回对象,如果找不到就委托给启动类加载器;
- 在启动类加载器中查询相应的类,如果找到就返回对象,如果找不到就继续下面流程;
- 在扩展加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就继续下面流程;
- 在应用程序类加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就返回 ClassNotFound 异常。
加载流程如下:
实现
双亲委派模型对于保证Java程序的稳定运作非常重要,但它的实现并不复杂。我们需要注意的是,在双亲委派模型中,类加载器之间的父子关系不是以继承的关系来实现,而是都使用组合关系来复用父加载器的代码的。
每个类加载器(除了引导类加载器)在实例化时都会被分配一个父类加载器的引用 parent,这个 parent 持有对父类加载器的引用。当一个类加载器需要加载一个类时,它会首先调用其父加载器的loadClass
方法尝试加载该类,这是双亲委派模型的体现。只有在父加载器无法加载该类时,子加载器才会尝试自己加载该类,该逻辑体现在Java.lang.ClassLoader
的 loadClass()
方法之中:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查该类是否已经被加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 父加载器不为空,使用父类的 loadClass() 尝试加载类
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) {
// 如果类仍未被加载,调用自己的 findClass()来加载这个类
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。