类加载与双亲委派
详解 类加载。
类加载过程
加载 → 验证 → 准备 → 解析 → 初始化
├──── 链接 ────┤
| 阶段 | 做什么 |
|---|---|
| 加载 | 读 .class,生成 Class 对象 |
| 验证 | 字节码安全、格式合法 |
| 准备 | 静态变量赋默认值(0 / null) |
| 解析 | 符号引用 → 直接引用 |
| 初始化 | 执行 <clinit>,静态块、静态字段实际赋值 |
static int x = 10:准备阶段 x=0,初始化阶段 x=10。
何时初始化(主动引用)
new实例- 访问静态字段/静态方法(非编译期常量)
Class.forName()- 子类初始化前先初始化父类
- 含
main的主类
不初始化:子类引用父类静态字段(只初始化父类);MyClass[] arr = new MyClass[10];编译期常量 static final。
类加载器层次
Bootstrap(C++,Java 中为 null)
└─ Extension
└─ Application(classpath)
└─ Custom ClassLoader
双亲委派模型
流程:收到加载请求 → 先委派给父加载器 → 父找不到 → 子才自己加载。
加载 com.example.User
→ App 委派 Extension
→ Extension 委派 Bootstrap
→ Bootstrap 找不到
→ Extension 找不到
→ App 从 classpath 加载
目的:
- 保证 核心类(
java.lang.*)由 Bootstrap 加载,防止伪造(如自定义java.lang.String) - 避免类重复加载
打破双亲委派(了解)
| 场景 | 做法 |
|---|---|
| SPI(JDBC、JNDI) | 线程上下文类加载器 TCCL |
| Tomcat | WebApp 优先加载自己的类,隔离多应用 |
| 热部署 / 插件 | 自定义 ClassLoader 加载新 jar |
常见面试题
Q:Class.forName 和 ClassLoader.loadClass 区别?
A:forName 默认会初始化类;loadClass 通常只加载不初始化(取决于实现与调用链)。
Q:同一个类可以被两个 ClassLoader 加载吗?
A:可以加载两份,在 JVM 中视为不同类,不能互相赋值(Tomcat 隔离)。
Q:准备和初始化区别?
A:准备赋默认值;初始化执行 static 块和显式赋值。