Skip to content

双亲委派机制是什么

双亲委派机制(Parents Delegation Model)是Java虚拟机(JVM)中类加载器(Class Loader)加载类时所遵循的一种层级化的加载策略。它规定了类加载器在尝试加载一个类时,应该如何协作和分配任务。这个机制并不是一个强制性的约束模型,而是Java设计者推荐的一种类加载器实现方式,并被JDK中默认的类加载器所遵循。

核心思想:

当一个类加载器收到加载一个类的请求时,它首先不会自己尝试去加载这个类,而是会把这个请求委派给它的父类加载器去完成。每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器(Bootstrap Class Loader)。只有当父类加载器在其搜索范围内无法找到所需的类,并反馈无法完成加载请求时,子类加载器才会尝试自己去加载这个类。

类加载器的层级关系(典型的):

  1. 启动类加载器(Bootstrap Class Loader):

    • 这是最顶层的加载器,由C++实现,是JVM自身的一部分。
    • 它没有“父加载器”(在Java层面看来,其getParent()方法返回null)。
    • 负责加载Java的核心类库,如JAVA_HOME/jre/lib/rt.jarresources.jar中的类(Java 8及以前),或者Java 9及以后模块化系统中的核心模块(如java.base)。
  2. 扩展类加载器(Extension Class Loader):

    • sun.misc.Launcher$ExtClassLoader实现。
    • 它的父加载器是启动类加载器。
    • 负责加载JAVA_HOME/jre/lib/ext目录下的JAR包,或者由java.ext.dirs系统属性指定的路径中的类库。
  3. 应用程序类加载器(Application Class Loader,也称系统类加载器 System Class Loader):

    • sun.misc.Launcher$AppClassLoader实现。
    • 它的父加载器是扩展类加载器。
    • 负责加载用户类路径(CLASSPATH环境变量、-cp-classpath 命令行参数、java.class.path系统属性)上指定的类库。开发者编写的大部分Java类都是由这个加载器加载的。
  4. 自定义类加载器(Custom Class Loader):

    • 开发者可以继承java.lang.ClassLoader类来创建自己的类加载器,以实现特定的类加载需求(如从网络加载、解密后加载等)。
    • 自定义类加载器在创建时,通常会指定一个父类加载器(默认是应用程序类加载器)。

工作流程:

  1. 当一个类加载器(比如应用程序类加载器)接收到加载类com.example.MyClass的请求时。
  2. 它首先检查这个类是否已经被自己加载过了。如果已加载,直接返回缓存的java.lang.Class对象。
  3. 如果未加载,它不会立即自己去查找和加载,而是将这个请求委派给它的父类加载器(即扩展类加载器)。
  4. 扩展类加载器收到请求后,也先检查自己是否已加载过此类。如果未加载,它继续将请求委派给它的父类加载器(即启动类加载器)。
  5. 启动类加载器收到请求后,尝试在其负责的路径(如核心库路径)中查找并加载com.example.MyClass
    • 如果启动类加载器找到了并成功加载了该类,它会将加载的Class对象逐层返回给发起请求的子加载器,最终返回给应用程序。加载过程结束。
    • 如果启动类加载器在其搜索路径下没有找到该类,它会告知其子加载器(扩展类加载器)自己无法加载。
  6. 扩展类加载器在收到父加载器无法加载的通知后,才会尝试自己在其负责的路径(如ext目录)中查找并加载com.example.MyClass
    • 如果扩展类加载器找到了并成功加载,结果逐层返回。
    • 如果也找不到,它会告知其子加载器(应用程序类加载器)自己无法加载。
  7. 最后,应用程序类加载器在收到父加载器(扩展类加载器)无法加载的通知后,才会尝试自己在其负责的路径(如用户CLASSPATH)中查找并加载com.example.MyClass
    • 如果应用程序类加载器找到了并成功加载,加载过程结束。
    • 如果仍然找不到,它会抛出ClassNotFoundExceptionNoClassDefFoundError

优点:

  1. 避免类的重复加载: 由于加载请求会向上委派,如果一个类已经被某个父加载器加载过了,那么这个父加载器会直接返回已加载的Class对象,子加载器就不会再次加载。这保证了在JVM中,对于同一个全限定名的类,只存在一个由特定类加载器加载的Class对象实例,确保了类的唯一性。

  2. 保证Java核心库的安全性(沙箱安全机制): 核心的Java API类(如java.lang.Object, java.lang.String等)总是由最顶层的启动类加载器加载。这意味着用户无法通过编写一个与核心库同名的恶意类来替换掉系统类,因为无论哪个加载器收到加载这类核心类的请求,最终都会委派给启动类加载器去加载。这防止了核心API被篡改,保护了Java平台的安全。

  3. 清晰的类加载层级和职责划分: 使得不同来源的类由不同层级的加载器负责,结构清晰。