Java类加载机制核心是“类何时进内存、怎么进、进后怎么用”:主动使用触发初始化,双亲委派保障安全与一致性,加载/验证/准备/解析/初始化五阶段各司其职,确保类正确、安全、可控地进入JVM并运行。

掌握Java类加载机制,关键不是死记阶段名称,而是理解每个环节“做什么、为什么做、不这么做会怎样”。它不是孤立知识点,而是贯穿JVM内存管理、安全模型和框架设计的底层逻辑。
紧扣“类何时进内存、怎么进、进后怎么用”三条主线
所有类加载问题都可以归到这三个问题里:
-
何时进:区分“被动引用”和“主动使用”。比如访问
public static final int MAX = 100不会触发类初始化(编译期常量内联),但访问public static int COUNT = 10就会;new A[10]只创建数组类,不加载A类本身。 -
怎么进:聚焦双亲委派的执行路径——子加载器收到请求先交父类,父类无法加载(如找不到class文件)才自己动手。重点想清楚:为什么
java.lang.String永远由Bootstrap加载器加载?如果绕过委派自己加载一个同名String,会发生什么? -
进后怎么用:准备阶段赋默认值(
int a→0)、初始化阶段才执行a = 5和静态块;符号引用(常量池里的类名/方法名)在解析阶段才转成真实内存地址。
用对比法吃透五个核心阶段
把加载、验证、准备、解析、初始化当成一套流水线,每个环节解决一个具体问题:
- 加载:只管“搬进来”,不检查内容是否合法。字节码可以来自jar、网络、甚至运行时生成(CGLib)。
- 验证:四道安检——魔数和版本号(格式对不对)、继承关系(是否继承final类)、字节码指令(会不会跳到非法地址)、符号引用(引用的类真存在吗)。
-
准备:只分配静态变量内存并设零值(
static int x→0,static Object o→null),不执行任何赋值语句。 -
解析:把常量池里的
"com.example.User"这种字符串,替换成方法区中真实的Class对象指针。 -
初始化:真正执行Java代码——按顺序跑静态变量赋值和静态块,且父类
<clinit>一定先于子类执行。
结合真实场景反推机制设计意图
脱离场景背流程容易忘,代入问题再回头找答案才牢固:
立即学习“Java免费学习笔记(深入)”;
- Tomcat为什么每个WebApp用独立的ClassLoader?——为实现应用隔离,避免不同应用的同名类互相干扰。
- Spring热部署怎么做到改完代码不重启?——用新ClassLoader加载新类,旧ClassLoader连带旧类实例被GC回收。
- 为什么自定义类加载器要重写
findClass()而不是loadClass()?——因为后者自带双亲委派逻辑,直接重写会破坏安全机制。 - OSGi插件系统怎么实现模块热插拔?——靠细粒度的类加载器划分和动态导出/导入包策略。
动手验证比看十遍文档更有效
写几行简单代码,用-verbose:class参数运行,观察控制台输出的类加载顺序:
- 新建一个空类
A,在main里只写A.class,看是否加载; - 加个
static { System.out.println("init A"); },再运行,确认初始化时机; - 让
B extends A,在main中new B(),观察A和B的加载与初始化先后; - 用
ClassLoader.getSystemClassLoader().loadClass("A")代替Class.forName("A"),对比是否触发初始化。


















