Java类加载的护城河:深入探究双亲委派机制( 二 )

通过前面对几种类加载的了解,对这个输出结果应该问题不大 。
但是可能有几个小疑问:
1、 System.out.println(
String.class.getClassLoader());这个语句,为何输出时null?
因为System类是Java核心类库中的类,它是由引导类加载器加载 。引导类加载器是JVM的内置加载器,由C++实现,因此在Java中就输出为null 。
2、System.out.println("the bootstrapLoader : " + bootstrapLoader);为何也输出为null? 这里extClassloader.getParent()获取扩展类加载器的父加载器,即引导类加载器,其由C++实现,因此在Java中就输出也就为null 。
类加载初始化过程

Java类加载的护城河:深入探究双亲委派机制

文章插图
 
如上类运行加载全过程图,可知在JVM启动的过程中,会有一系列的初始化操作,包括创建类加载器、加载核心类库等 。在这个初始化的过程,C++(其实是JVM自身调用,因为JVM底层是C++实现,从底层的角度,就是C++代码调用Java)调用Javasun.misc.Launcher类的构造方法 Launcher()创建实例 。
在Launcher构造方法的内部,会创建两个类加载器:
  • sun.misc.Launcher.ExtClassLoader(扩展类加载器)
  • sun.misc.Launcher.AppClassLoader(应用程序加载器)
Launcher构造器核心源码:
Java类加载的护城河:深入探究双亲委派机制

文章插图
 
JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序 。
二、什么是双亲委派?
双亲委派是一种类加载的机制 。如果一个类加载器接到加载类的请求时,它首先不会自己尝试加载这个类,而是把这个请求任务委托给其父加载器去完成,依次递归,如果父加载器可以完成类加载任务,就成功返回 。只有父加载器无法完成此加载任务时,才自己去加载 。
JVM类加载器的层级结构图:
Java类加载的护城河:深入探究双亲委派机制

文章插图
 
我们直接先来看一下应用程序类加载器(AppClassLoader)加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法, 该方法核心源码:
Java类加载的护城河:深入探究双亲委派机制

文章插图
 
该方法的大体逻辑为:
  1. 首选,会检查自己加载器缓存,查看自己是否已经加载过目标类,如果已经加载过,直接返回 。
  2. 如果此类没有被加载过,则判断一下是否有父加载器;如果有父加载器,则由父加载器(即 调用c = parent.loadClass(name, false);),如果没有父加载器则调用bootstrap类加载器来加载 。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前的类加载器的findClass方法(即c = findClass(name);)来完成类加载 。
双亲委派机制简单点说就是:先找父亲加载,不行再由儿子自己加载 。
三、为什么要设计双亲委派机制?
  • 沙箱安全机制:核心类库由引导类加载器(Bootstrap ClassLoader)加载,防止恶意类替代核心类 。不同类加载器加载的类相互隔离,防止类之间的冲突 。例如自己写的java.lang.String类是不会被加载的 。
  • 保证类的唯一性: 双亲委派机制确保在JVM中同一个类只会被加载一次,避免了类的重复加载,保证了类的唯一性 。
  • 保证类的一致性: 类加载器的层次结构保证了类的一致性 。当一个类加载器需要加载一个类时,它首先会委派给其父加载器去尝试加载 。如果父加载器无法加载该类,子加载器才会尝试加载 。这种委派链式查找保证了类的一致性 。
如果你对唯一性 和 一致性有些混淆,那我们可以借助以下的例子进行帮助理解:
唯一性: 就像每个人的身份证号码都是独一无二的 。在类加载机制中,就像每个类在Java中都有唯一的类加载器来加载,保证不同的类拥有不同的加载器,避免了类之间的冲突和混淆 。
一致性: 无论在什么情况下使用身份证,一个人的身份证号码都是不变的 。在类加载中,一致性指的是无论通过哪个类加载器加载同一个类,其类定义,在整个应用中都是一致性 。
运行尝试加载自己写的java.lang.String类:
typescript复制代码package java.lang;public class String {public static void main(String[] args) {System.out.println(">>> Hello String Class >>>");}}


推荐阅读