精选·Java与并发

类加载机制与双亲委派模型

91学AI·2026/7/13·9 阅读

考察点

类加载是 JVM 基础题里区分度较高的一道,背概念和真理解差距很明显。面试官想听的是:双亲委派到底解决了什么问题(安全 + 避免重复加载),以及现实世界里哪些场景必须打破它(JDBC SPI、Tomcat、OSGi)。大数据岗位里,Hive/Spark 的用户 UDF、Flink 的动态加载都涉及类加载器隔离,能联系实际会加分。追问常往「怎么自定义类加载器」「一个类什么时候触发初始化」「ClassNotFoundException 和 NoClassDefFoundError 区别」走。

参考答案

类加载的五个阶段

类从被加载到内存到卸载,生命周期里关键的是这五步:

  1. 加载:通过类的全限定名找到 class 字节流,读进内存,在堆中生成这个类的 Class 对象作为访问入口。
  2. 验证:校验字节码是否符合规范——文件格式、元数据语义、字节码指令、符号引用,保证不会危害虚拟机自身安全。
  3. 准备:给类的静态变量分配内存并设默认值(int 是 0,不是代码里赋的值)。注意是 static 变量,实例变量此时还不存在。
  4. 解析:把常量池里的符号引用(字符串形式的类名、方法名)替换成直接引用(内存指针)。
  5. 初始化:执行类构造器 <clinit>() 方法,即静态变量赋值和静态代码块。这是真正执行程序员代码的一步。

初始化的触发时机是常考点:new、读写静态字段(final 编译期常量除外)、调静态方法、反射调用、初始化子类时父类未初始化、包含 main 的启动类。被动引用不触发——比如通过子类引用父类的静态字段,只有父类初始化。

三层类加载器与双亲委派

JVM 自带的类加载器分三层:

  • Bootstrap(启动类加载器,C++ 实现):加载 JAVA_HOME/lib 下的核心类,rt.jar 里的 java.lang.* 等。
  • Extension(扩展类加载器):加载 lib/ext 或 java.ext.dirs 指定路径。
  • Application(应用类加载器):加载 classpath 上的类,平时写的代码都归它管。

双亲委派的工作流程:一个类加载器收到加载请求时,自己先不加载,而是委派给父加载器,父再委派给祖父,一直到 Bootstrap;父加载器能加载就由父加载,父加载不了(在自己的搜索范围里找不到)才轮到子加载器尝试。

这样做解决两个根本问题:一是避免重复加载,一个类由特定加载器加载一次,全 JVM 唯一;二是安全,核心类库由 Bootstrap 加载,用户没法用一个伪造的 java.lang.String 替换掉系统类——你写的 java.lang.String 永远会被委派到 Bootstrap,而 Bootstrap 只加载它信任的核心类。类的唯一性由「全限定名 + 类加载器」共同决定,同一个类被不同加载器加载,在 JVM 眼里是两个类,instanceof 都为 false。

必须打破委派的场景

双亲委派不是铁律,现实里有几类典型破坏:

SPI 机制(JDBC 最典型):JDBC 的 DriverManager 是核心类,由 Bootstrap 加载,但具体的数据库驱动(MySQL Connector 等)在用户的 classpath 下,Bootstrap 加载不到它们。解决办法是线程上下文类加载器——父加载器委托子加载器(Application)去加载驱动实现,等于委派的逆向。Java SPI(ServiceLoader)全靠这个机制。

Tomcat 等 Web 容器:一个容器跑多个 Web 应用,应用之间引的 jar 版本可能冲突(一个用 Spring 4 一个用 Spring 5),必须隔离。所以每个 Web 应用有自己的 WebappClassLoader,加载 WEB-INF 下的类时优先自己加载,加载不到才找父加载器,和双亲委派反过来。共享的公共库仍走委派,保证容器核心类不被污染。

热部署/模块化(OSGi):OSGi 里每个 bundle 有自己的类加载器,委派关系是一张网而不是树,按导入导出规则委派,实现模块热替换。

工程实践

写自定义类加载器只需继承 ClassLoader 重写 findClass(不要重写 loadClass,否则就破坏了委派模型),典型用途:从非标准位置(网络、数据库、加密文件)加载类、做类隔离。大数据平台跑用户提交的 UDF jar 时,通常给每个任务建独立的 URLClassLoader,任务结束丢弃,防止不同用户同名类互相污染,这也是 Flink ChildFirstClassLoader 的思路。

可能的追问

  • ClassNotFoundException 和 NoClassDefFoundError 的区别? 前者是 Class.forName/loadClass 显式加载时找不到类,是个受检异常;后者是编译期有、运行期类定义丢了(比如 jar 包版本不对),是 Error。
  • 怎么自定义类加载器又不破坏委派? 重写 findClass 即可,loadClass 默认实现会走完整委派流程,找不到才回调你的 findClass。
  • 同一个类被两个加载器加载会怎样? JVM 认为是两个不同的类,静态变量各有一份,互相强转抛 ClassCastException。这也是 Tomcat 应用隔离的底层原理。
  • 类卸载的条件? 该类的所有实例被回收、Class 对象无引用、加载它的类加载器被回收——三个条件同时满足,所以自定义加载器加载的类才能卸载,这也是热部署的基础。

评论 (0)

暂无评论,快来抢沙发吧!

91学AI

© 2026 91学AI · 按岗位学 AI 与大数据. All rights reserved.