Java类加载系统需围绕生命周期、加载器结构、委派逻辑、破委派场景四主线掌握:生命周期分加载、验证、准备、解析、初始化五阶段;加载器呈Bootstrap→Extension→Application三层结构;双亲委派是loadClass默认流程,保障核心类安全;热部署、SPI、依赖隔离三场景需打破委派。

Java 类加载系统不是靠“看教程”就能掌握的,关键在于理清脉络、抓住主线、动手验证。
真正有效的梳理方式,是围绕生命周期、加载器结构、委派逻辑、破委派场景这四个核心展开,而不是通读长篇文档。
生命周期要分清阶段职责
类从文件到可执行,不是一气呵成,而是分步推进:
- 加载:找字节码(本地文件、JAR、网络、动态生成),放进方法区,生成 Class 对象(注意:不是 new 出来的实例)
- 验证:检查字节码是否合规(格式、语义、字节码逻辑),防止恶意或损坏代码
- 准备:给 static 变量分配内存,设默认值(比如 int 设为 0,引用设为 null),不执行赋值语句
-
解析:把符号引用(如
#23)替换成直接引用(如内存地址),常发生在初始化前,但也可延迟到首次使用时 -
初始化:真正执行
<clinit>方法——也就是静态块和 static 变量显式赋值(顺序按源码书写顺序)
举例:
static int x = getValue();中getValue()是在初始化阶段才调用,不是准备阶段。立即学习“Java免费学习笔记(深入)”;
类加载器结构必须亲手查
别背名字,用几行代码立刻看清当前环境的加载器链:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
System.out.println("当前类加载器: " + YourClass.class.getClassLoader());
System.out.println("父加载器: " + YourClass.class.getClassLoader().getParent());
System.out.println("祖父加载器: " + YourClass.class.getClassLoader().getParent().getParent()); // 输出 null → 表示 Bootstrap
System.out.println("String 加载器: " + String.class.getClassLoader()); // 输出 null → 由 Bootstrap 加载结果会清晰呈现三层结构:
-
Bootstrap(C++ 实现,加载
rt.jar等核心类,Java 层不可见) -
Extension(加载
jre/lib/ext下的类) -
Application(即 AppClassLoader,加载
-cp或CLASSPATH指定路径的类)
双亲委派不是规则,是默认实现逻辑
它本质是 ClassLoader.loadClass() 方法里的固定流程:
- 先查缓存(
findLoadedClass) - 父加载器不为空,就先让父去
loadClass - 父返回
ClassNotFoundException,才轮到自己调findClass - 最后做
resolveClass(链接)
这个机制保障了 java.lang.Object 不会被用户自定义类替换——安全底线。但它的代价是:无法让子加载器优先加载同名类(比如 Web 应用想用自己的 json-lib,却被 Bootstrap 或 Ext 加载了旧版)。
打破委派不是炫技,而是解决真实问题
以下三类场景必须绕过双亲委派:
-
热部署/插件化:每个模块用独立加载器,避免类冲突,Tomcat 就为每个 WAR 包创建
WebAppClassLoader -
SPI 服务加载:
ServiceLoader使用线程上下文类加载器(TCCL),让 Bootstrap 加载的java.util.ServiceLoader能加载应用层的实现类 -
隔离依赖:Flink、Spark 用
ChildFirstClassLoader,优先从子路径加载,再委托父(反向委派)
自定义加载器只需重写 findClass,别动 loadClass —— 否则容易破坏委派链或引发重复加载。
不复杂但容易忽略

















