类加载的五个核心阶段是加载、验证、准备、解析、初始化,分别负责搬入字节码、校验安全性、分配静态变量内存并设默认值、符号引用转直接引用、执行静态代码块和赋值;双亲委派确保类唯一性与安全隔离,破坏它需权衡如Tomcat应用隔离等实际需求。

直接从类加载的“触发点”入手学最有效。别一上来就背七个阶段,先搞清楚什么时候JVM真会去加载一个类——比如写new MyClass()、调用MyClass.staticMethod()、执行Class.forName("MyClass"),这些才是真实入口。掌握这些场景,再回看每个阶段做了什么,逻辑自然就串起来了。
聚焦五个核心阶段,分清职责和顺序
加载、验证、准备、解析、初始化这五步是硬核主线,后两个(使用、卸载)属于运行时行为,初学可暂放一边。重点理清:
- 加载:只负责“搬进来”——找.class文件、读字节码、生成Class对象,不执行任何Java代码
- 验证:检查字节码是否合规(比如魔数是否为0xCAFEBABE),防止恶意或损坏的类被载入
-
准备:给静态变量分配内存并设默认值(如
int x→0,String s→null),不是初始值 - 解析:把类、字段、方法的符号引用(比如“java/lang/Object”)转成内存中真实的地址引用
-
初始化:真正执行
<clinit>方法——也就是static块和static变量的赋值语句
吃透双亲委派,理解它为什么存在
双亲委派不是为了炫技,而是解决两个根本问题:类的唯一性 + 安全隔离。它的流程很简单:当前类加载器先不自己干,而是把任务往上交给父加载器;父不行再交爷爷,直到Bootstrap;只有顶层都找不到,才自己动手。
关键要明白:
立即学习“Java免费学习笔记(深入)”;
- 同一个类名,被不同类加载器加载 → JVM视为两个完全无关的类(
clazz1 == clazz2为false) - Bootstrap加载
java.lang.String,AppClassLoader就不能再定义同名类冒充它——这就是沙箱机制的底层保障 - 破坏双亲委派有正当理由:比如Tomcat为每个Web应用隔离类、OSGi实现模块热替换,但必须清楚代价
结合Spring Boot和反射实战加深理解
纯理论容易飘,拉到真实场景里立刻落地:
- Spring Boot启动时用
LaunchedURLClassLoader加载BOOT-INF/classes,它绕开了传统双亲委派的“自上而下”,改成“子优先”——这样才能让应用自己的类覆盖依赖里的同名类 - 用
Class.forName("com.example.Service")不仅加载类,还会触发初始化;而Service.class只是获取已加载的Class对象,不触发初始化——这个差别直接影响静态块执行时机 - 遇到
NoClassDefFoundError,大概率是解析阶段失败(类在加载时存在,但初始化时某个依赖类缺失);ClassNotFoundException则是加载阶段就找不到字节码——定位异常得看发生在哪一环
学的时候手边备个简单Demo:写一个空类,加static块和静态字段,分别用Class.forName、.class、new触发,观察输出顺序。一遍不够就两遍,类加载不是靠记,是靠看见。


















