深入理解JVM类加载器与双亲委派模型
2026/8/21 9:18:53 网站建设 项目流程

前言

在Java技术栈中,类加载机制是每一位开发者都应该深入理解的核心知识。无论是日常开发中遇到的ClassNotFoundException,还是框架层面的热部署、模块隔离,背后都离不开类加载器与双亲委派模型的支撑。本文将带你从零开始,系统性地理解JVM类加载器的分类、双亲委派模型的工作原理,以及这一设计背后的深意。

一、什么是类加载器?

类加载器(ClassLoader)是JVM运行时系统的重要组成部分,负责在运行时查找字节码文件,将字节码文件加载到内存中,并将其转化为运行时数据结构。

简单来说,类加载器的主要作用就是动态加载Java类的字节码(.class文件)到JVM中,在内存中生成一个代表该类的Class对象。字节码可以是Java源程序经过javac编译得来,也可以是通过工具动态生成或者通过网络下载得来。

值得注意的是,JVM启动时并不会一次性加载所有的类,而是根据需要动态加载——大部分类在具体用到时才会被加载,这种方式对内存更加友好。

二、类加载器的分类

从JVM的角度看,类加载器主要分为两大类:Bootstrap ClassLoader和其他类加载器。从Java开发者的角度,我们需要了解以下四种:

2.1 启动类加载器(Bootstrap ClassLoader)

这是最顶层的类加载器,由C++实现,是虚拟机自身的一部分。它负责加载<JAVA_HOME>/lib目录下的核心类库(如rt.jarresources.jarcharsets.jar等),以及被-Xbootclasspath参数指定路径下的所有类。

在Java代码中,我们无法直接获取到Bootstrap ClassLoader的引用——通过getClassLoader()获取时通常返回null。我们常用的java.lang包下的类(如StringObject),都是由Bootstrap ClassLoader加载的。

2.2 扩展类加载器(Extension ClassLoader)

扩展类加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>/lib/ext目录下的类库,或者被java.ext.dirs系统变量指定路径中的所有类库。

2.3 应用程序类加载器(Application ClassLoader)

应用程序类加载器由sun.misc.Launcher$AppClassLoader实现,负责加载用户CLASSPATH环境变量指定路径中的所有类库。如果没有自定义类加载器,这就是Java程序中默认使用的类加载器。

2.4 用户自定义类加载器(Custom ClassLoader)

用户可以通过继承java.lang.ClassLoader类来实现自定义类加载器。常见的应用场景包括:隔离加载类、修改类加载方式、扩展类加载源(如从网络或数据库加载)、防止源代码泄露(对字节码加密后自定义加载)等。

注意:JDK 9之后引入了模块化系统,扩展类加载器(Extension ClassLoader)被改名为平台类加载器(Platform ClassLoader)。

三、双亲委派模型

3.1 什么是双亲委派模型?

双亲委派模型(Parent Delegation Model)是JVM类加载器的核心工作机制。它要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里的"父子关系"一般不是通过继承实现的,而是通过组合关系来复用父加载器的代码。

一个小插曲:其实"双亲"这个翻译容易让人误解——每个类加载器只有一个父加载器,翻译成"单亲委派"可能更准确,但"双亲委派模型"这个说法已经在国内广为流传,我们就按这个来理解就好。

3.2 工作流程

双亲委派模型的工作流程可以概括为以下几个步骤:

第一步:自底向上检查。当一个类加载器收到类加载请求时,它首先检查这个类是否已经被自己加载过了。如果已加载,直接返回;否则将请求委派给父类加载器。每一层都如此,直到最顶层的启动类加载器。

第二步:自顶向下尝试加载。如果启动类加载器在自己的加载路径中找到了这个类,就加载并返回;如果找不到,就由下一层(扩展类加载器)尝试加载;以此类推,直到最初收到请求的那个类加载器自己尝试加载。

第三步:加载失败则抛出异常。如果所有类加载器都无法加载这个类,最终抛出ClassNotFoundException

可以用一张图来帮助理解(向上检查,向下加载):

Bootstrap ClassLoader ↑ (委托) | (加载) Extension ClassLoader ↑ (委托) | (加载) Application ClassLoader ↑ (委托) | (加载) Custom ClassLoader

3.3 源码实现

双亲委派模型的实现逻辑集中在java.lang.ClassLoaderloadClass()方法中,核心代码如下:

protectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){// 首先检查这个类是否已经被加载过了Class<?>c=findLoadedClass(name);if(c==null){try{if(parent!=null){// 存在父类加载器,委托给父类加载c=parent.loadClass(name,false);}else{// 父类加载器为null,说明是启动类加载器c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){// 父类加载器无法完成加载}if(c==null){// 父类加载器无法加载,自己尝试加载c=findClass(name);}}returnc;}}

3.4 为什么要采用双亲委派模型?

双亲委派模型的设计并非偶然,它主要解决了以下几个关键问题:

1. 避免类的重复加载。通过自底向上的检查机制,同一个类在全JVM范围内只会被加载一次,保证了类的唯一性。

2. 保证核心类库的安全性。如果没有双亲委派模型,用户完全可以自己编写一个java.lang.String类并放在ClassPath中,导致系统出现多个不同的String类,Java类型体系中最基础的行为将无法保证。有了双亲委派模型,所有对java.lang.String的加载请求最终都会到达Bootstrap ClassLoader,加载rt.jar中的标准String类。

3. 保证Java平台的稳定性。核心类库总是由最顶层的类加载器加载,确保了不同类加载器环境中核心类的一致性。

四、打破双亲委派模型

双亲委派模型并不是一种强制性的约束,只是JDK官方推荐的一种方式。在某些场景下,我们需要打破这一模型。

4.1 第一次破坏:JDK 1.2之前的兼容性

双亲委派模型在JDK 1.2之后才被引入,但ClassLoader抽象类在Java的第一个版本中就已经存在。为了兼容已有的自定义类加载器代码,Java设计者在ClassLoader中新增了findClass()方法,引导用户重写findClass()而不是loadClass(),这样既保留了自定义能力,又能让新的类加载器符合双亲委派规则。

4.2 第二次破坏:基础类型回调用户代码(线程上下文类加载器)

双亲委派模型的一个天然缺陷是:子加载器可以看到父加载器的内容,但反之不行。如果基础类型(由Bootstrap ClassLoader加载)需要回调用户代码(在ClassPath中,由Application ClassLoader加载),就会遇到问题。

一个典型的例子是JNDI服务。JNDI的代码在JDK 1.3时被加入rt.jar,由启动类加载器加载。但JNDI需要调用由第三方厂商实现并部署在ClassPath下的SPI(服务提供者接口)代码。启动类加载器无法加载这些代码。

为了解决这个问题,Java引入了线程上下文类加载器(Thread Context ClassLoader)。通过Thread.setContextClassLoader()方法设置后,基础类可以通过这个加载器来加载用户代码。这实质上打破了双亲委派模型的单向层次结构。

4.3 第三次破坏:对动态性的追求(OSGi与热部署)

OSGi(Open Service Gateway Initiative)是实现模块化热部署的典型代表。在OSGi环境中,每个模块(Bundle)都有自己的类加载器。当需要更换一个Bundle时,只需将Bundle连同其类加载器一起换掉,即可实现代码的热替换。

OSGi的类加载器不再遵循双亲委派模型的树状结构,而是发展成了更加复杂的网状结构。收到类加载请求时,OSGi会按照特定的顺序在多个Bundle中搜索类。

除此之外,Tomcat为每个Web应用创建独立的WebAppClassLoader,打破了双亲委派模型(优先自己加载而非委托父加载器),实现了不同Web应用之间的类隔离。

五、总结

类加载器与双亲委派模型是JVM体系中的核心设计之一。理解这一机制,不仅有助于我们:

  • 排查日常开发中遇到的类加载相关异常
  • 理解Spring、Tomcat等框架的类隔离与热部署原理
  • 在需要时实现自定义类加载器,满足特定业务需求

双亲委派模型通过层次化的委托机制,巧妙地平衡了安全性(防止核心类被篡改)、一致性(保证类的唯一性)和灵活性(允许自定义类加载器)三者之间的关系。尽管在某些场景下它需要被打破,但作为Java平台二十多年来稳定运行的重要基石,双亲委派模型的设计思想依然值得我们深入品味与学习。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询