news 2026/4/20 12:51:09

JVM双亲委派模型详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM双亲委派模型详解

JVM 双亲委派模型(Parent Delegation)详解

关键词:ClassLoader类的唯一性安全隔离可替换/可插拔

本文讲清楚三件事:

  1. 双亲委派到底是什么规则(不是“父类加载器加载”这么一句话)。
  2. 为什么 JVM 要这么设计(安全 + 统一 + 隔离)。
  3. 现实里怎么被打破(Tomcat / SPI / OSGi / 热部署等),以及你排查 ClassLoader 问题的套路。

1. 类加载器体系:谁负责加载谁?

JVM 里“把.class变成Class<?>”的东西叫类加载器(ClassLoader)。常见三层:

  • Bootstrap ClassLoader(启动类加载器)

    • 用 C/C++ 实现(HotSpot 里不是 Java 对象)
    • 负责加载 JDK 核心类:java.*javax.*(部分)、jdk.*
    • 类路径:典型是jre/libjmods(Java 9+)
  • Platform ClassLoader(平台类加载器)(Java 9+)

    • 负责加载平台模块(以前很多rt.jar里拆出来的东西)
  • Application ClassLoader(应用类加载器)

    • 负责加载你应用的 classpath(-cp/CLASSPATH)下的类

另外还有:

  • 自定义 ClassLoader:Tomcat、Spring Boot、OSGi、插件框架、脚本引擎等经常自己搞一套。

你可以在代码里看看当前环境:

publicclassLoaderDemo{publicstaticvoidmain(String[]args){ClassLoadercl=LoaderDemo.class.getClassLoader();System.out.println("App: "+cl);System.out.println("Parent: "+cl.getParent());System.out.println("Grandparent: "+cl.getParent().getParent());// 通常为 null => BootstrapSystem.out.println("String loader: "+String.class.getClassLoader());// null => Bootstrap}}

2. 双亲委派模型:规则是什么?

一句话版本:“一个类加载请求先交给父加载器,父加载器不行才自己来。”

更精确一点(这个才是真正的“模型”):

  1. 先检查缓存:这个 ClassLoader 是否已经加载过这个类(避免重复定义)。
  2. 如果有父加载器:把加载请求委派给父加载器(递归向上)。
  3. 父加载器也加载不了(抛ClassNotFoundException):
    • 子加载器才会尝试调用自己的findClass()来加载。

伪代码(接近 JDKClassLoader#loadClass的逻辑):

protectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{// 1) 已加载?Class<?>c=findLoadedClass(name);// 2) 没加载过,先委派父加载器if(c==null){try{if(parent!=null){c=parent.loadClass(name);}else{c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){// 父加载器加载不了,走 3)}// 3) 父加载器不行,我自己找if(c==null){c=findClass(name);}}if(resolve)resolveClass(c);returnc;}

注意:这不是“必须”的 JVM 规则,而是“JDK 默认 ClassLoader 的实现策略”

  • JVM 规范并不强制你必须委派父加载器。
  • 但 JDK 提供的ClassLoader默认实现就是这个策略。
  • 你可以通过重写loadClass()来改变(这就是“打破双亲委派”的入口)。

3. 为什么要双亲委派?(设计动机)

3.1 安全:防止核心类被替换/伪造

如果没有委派机制,你完全可以在应用 classpath 里放一个java.lang.String,让 JVM 加载你的假 String —— 这会直接把安全边界打穿。

有了双亲委派:

  • 加载java.lang.String时,应用类加载器先问父加载器
  • 父加载器一路到 Bootstrap,Bootstrap 先把真正的java.lang.String加载了
  • 你的“假 String”根本没机会出场

3.2 一致性:同名核心类全 JVM 统一

比如java.util.List在整个 JVM 里必须是同一个Class对象,不然各种库之间根本没法协作。

3.3 隔离:不同层次的类彼此隔离又能共享基础能力

  • 上层(应用)依赖下层(JDK 核心)是常态
  • 下层不应该“反向”依赖上层
  • 委派天然保证“基础设施”稳定、“业务代码”可变

4. 类的唯一性:你踩坑多半都在这

类在 JVM 中的唯一性不是只看“类的全限定名”,而是:

Class = (ClassLoader, FullyQualifiedName)

也就是说:

  • 同名同包的类,只要由不同 ClassLoader加载,就是两个完全不同的类型。
  • 你会看到经典报错:
    • java.lang.ClassCastException: A cannot be cast to A
    • 或者NoSuchMethodError/LinkageError

例子(两个不同 Loader 加载同一个字节码):

Class<?>c1=loader1.loadClass("com.demo.User");Class<?>c2=loader2.loadClass("com.demo.User");System.out.println(c1==c2);// falseSystem.out.println(c1.getClassLoader());System.out.println(c2.getClassLoader());

5. 什么时候会“打破双亲委派”?(现实世界)

双亲委派是默认策略,但很多框架/容器为了功能必须“反着来”或“改一点”。

5.1 Tomcat:Web 应用隔离(经典“WebAppClassLoader”)

需求:

  • 多个 webapp 运行在同一个 JVM
  • 每个应用有自己的WEB-INF/lib
  • 不同应用可以用不同版本的同一个库
  • 但还得共享 servlet 规范 API

做法(简化):

  • 对大多数业务类:优先自己加载(child-first),保证隔离
  • 对 servlet/jsp 等容器 API:仍然委派父加载器,保证共享

这就是:部分 child-first,部分 parent-first

5.2 SPI(Service Provider Interface):父加载器反向用子加载器

典型:JDBC 驱动(历史上尤为经典)

  • java.sql.DriverManager是 Bootstrap 或平台类加载器加载的(在“父”里)
  • 驱动实现类(com.mysql.cj.jdbc.Driver)在应用 classpath(在“子”里)

如果严格双亲委派:

  • 父加载器看不到子加载器的类
  • DriverManager怎么加载驱动实现?

解决:线程上下文类加载器(TCCL, Thread Context ClassLoader)

  • 让“父层的代码”临时使用“子层的加载器”去加载 SPI 实现

获取方式:

ClassLoadertccl=Thread.currentThread().getContextClassLoader();

很多框架(JNDI、JAXP、日志门面等)都用过这个套路。

5.3 OSGi / 插件化:多版本共存、按需可卸载

OSGi 直接把“类可见性”做成模块依赖图:

  • A bundle 看不见 B bundle 的类,除非显式导入/导出
  • 版本也能隔离(同名包多版本共存)

这本质上就是一套更复杂的“类加载路由”。

5.4 热部署/热加载:替换 class

比如某些脚本引擎、热更新框架:

  • 通过“新建一个 ClassLoader”加载新版本 class
  • 旧 ClassLoader 及其 Class 变成“不可达”后才能被 GC 回收(前提是没有泄漏引用)

6. 怎么“打破”双亲委派?(技术手段)

双亲委派之所以能被破坏,是因为你可以重写loadClass()

  • parent-first(默认):先委派父加载器,再自己
  • child-first:先自己findClass(),找不到再委派父加载器

child-first 示例(非常简化,仅示意):

publicclassChildFirstClassLoaderextendsClassLoader{publicChildFirstClassLoader(ClassLoaderparent){super(parent);}@OverrideprotectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){Class<?>c=findLoadedClass(name);if(c==null){try{// 1) 先自己找(打破委派)c=findClass(name);}catch(ClassNotFoundExceptionignored){// 2) 自己找不到再问父c=super.loadClass(name,false);}}if(resolve)resolveClass(c);returnc;}}@OverrideprotectedClass<?>findClass(Stringname)throwsClassNotFoundException{// 从自定义路径读取字节码,然后 defineClass(...)thrownewClassNotFoundException(name);}}

现实里不会这么“裸写”,一般会配合 jar/目录扫描、缓存、黑白名单(哪些包 parent-first,哪些包 child-first)。


7. 排查 ClassLoader 问题的实战套路

7.1 先看“谁加载的”

任何诡异的类型转换/方法找不到,第一件事:

System.out.println(SomeClass.class.getClassLoader());System.out.println(SomeInterface.class.getClassLoader());

如果你看到:

  • 接口由 A loader 加载
  • 实现类由 B loader 加载
    那基本就能解释很多ClassCastException

7.2 典型症状 -> 典型原因

  • ClassCastException: X cannot be cast to X

    • 99% 是不同 ClassLoader 加载了同名类
  • NoSuchMethodError/NoSuchFieldError

    • 常见是依赖冲突:编译时用的版本和运行时实际加载版本不一致
    • 也可能是类加载顺序导致“加载到了旧版本”
  • LinkageError: loader constraint violation

    • 两个 loader 对同一个符号引用约束不一致(复杂但本质还是类型不统一)

7.3 Tomcat / Spring Boot 常见坑

  • web 容器里重复放了同一个 jar(WEB-INF/lib+ 容器 shared lib)
  • fat jar / 多层 classpath 导致版本 shadowing
  • 日志框架(slf4j / logback / log4j2)多实现共存导致冲突

8. 一张图把双亲委派讲明白

loadClass("com.demo.Foo") | +----------v-----------+ | AppClassLoader | +----------+-----------+ | 委派给 parent | +----------v-----------+ | PlatformClassLoader | +----------+-----------+ | 委派给 parent | +----------v-----------+ | BootstrapClassLoader | +----------+-----------+ | 找到/加载? yes -> 返回 | no (CNF) | 回到子加载器 findClass()

9. 记住这几句就够用了

  • 默认就是 parent-first:先父后子。
  • 核心目的:安全 + 一致性。
  • 类唯一性(ClassLoader, 类名),不是只有类名。
  • 打破委派的原因:隔离、多版本、插件化、SPI。
  • 排查第一步:打印getClassLoader(),看是不是“同名不同 loader”。

10. 延伸阅读建议

想深入的话,可以按这个顺序看:

  1. java.lang.ClassLoader源码:loadClass / findClass / defineClass
  2. TCCL:Thread#getContextClassLoader
  3. Tomcat ClassLoader 架构(Common / Catalina / Shared / WebApp)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 11:06:28

别再迷信大模型了!微软全新RL方法,让14B小模型“越级”挑战DeepSeek-R1,大海捞针轻松拿捏!

在当前大语言模型的发展中&#xff0c;长上下文推理能力的提升已成为关键研究方向。然而&#xff0c;构建具备高级长上下文推理能力的模型仍面临多重挑战。 首先&#xff0c;用于训练的理想问题需足够复杂以激发深度推理并支持从长上下文中动态检索关键信息&#xff0c;而且答…

作者头像 李华
网站建设 2026/4/17 16:18:37

1、探索 DB2 Express - C:免费且强大的数据库解决方案

探索 DB2 Express - C:免费且强大的数据库解决方案 1. 适用人群与书籍结构 对于数据库管理员(DBAs)、应用程序开发人员、顾问、软件架构师、产品经理、教师和学生等与数据库打交道或打算从事相关工作的人来说,有一个很好的资源可以帮助他们了解和使用数据库。这个资源不仅…

作者头像 李华
网站建设 2026/4/18 13:27:08

11、DB2 数据库安全与备份恢复全解析

DB2 数据库安全与备份恢复全解析 1. DB2 数据库安全基础 在 DB2 数据库系统中,有两个重要的用户组与安全访问密切相关: - DB2ADMNS :该组和本地管理员通过操作系统对所有 DB2 对象拥有完全访问权限。 - DB2USERS :此组通过操作系统对所有 DB2 对象具有读取和执行访…

作者头像 李华
网站建设 2026/4/16 13:34:05

全国知名省级网络安全大赛

以下是全国范围内主要和知名的省级及国家级网络安全大赛的梳理&#xff0c;你可以根据自身情况选择参加。一、 国家级综合赛事&#xff08;通常下设省赛区&#xff09;这些大赛影响力最大&#xff0c;覆盖面最广&#xff0c;通常先举办省赛选拔&#xff0c;优胜者进入全国总决赛…

作者头像 李华
网站建设 2026/4/18 23:06:18

计算机网络复习全书(详细整理)

[TOC](计算机网络复习全书目录)前言&#xff1a;为什么你需要这份指南&#xff1f;计算机网络是IT世界的基石&#xff0c;也是每一位计算机、软件工程及相关专业学生必须掌握的核心课程。面对教材的厚重、概念的繁多和计算题的烧脑&#xff0c;期末复习往往令人望而却步。这份《…

作者头像 李华
网站建设 2026/4/16 13:10:32

4、GTK+ 容器小部件全解析

GTK+ 容器小部件全解析 在 GTK+ 开发中,容器小部件是构建用户界面的重要组成部分,它们可以帮助我们组织和排列其他小部件。容器小部件主要分为装饰器容器和布局容器两类。 容器小部件概述 容器类的主要目的是让一个父小部件包含一个或多个子小部件。GTK+ 中有两种类型的容…

作者头像 李华