一、类加载的时机
-
主动引用(触发类初始化):
-
创建类实例(new)
-
访问类的静态变量/方法(非final)
-
反射调用(Class.forName())
-
初始化子类时父类未初始化
-
虚拟机启动时指定的主类
-
-
被动引用(不触发初始化):
-
通过子类引用父类静态字段(仅初始化父类)
-
定义类引用数组(不会触发类初始化)
-
访问编译期常量(static final)
-
二、类加载过程
1. 加载(Loading)
-
主要任务:
-
通过全限定名获取类的二进制字节流
-
将字节流转化为方法区的运行时数据结构
-
在堆中生成Class对象作为访问入口
-
-
数据来源:
-
ZIP/JAR包
-
网络动态加载
-
运行时计算生成(动态代理)
-
其他文件生成(JSP)
-
数据库读取
-
2. 验证(Verification)
-
文件格式验证:魔数、版本号等
-
元数据验证:语义分析(是否有父类、是否final等)
-
字节码验证:程序逻辑校验
-
符号引用验证:解析阶段发生,检查引用能否找到对应类
3. 准备(Preparation)
-
为类变量(static变量)分配内存并设置初始零值
-
static final常量在此阶段直接赋值
4. 解析(Resolution)
-
将符号引用转换为直接引用
-
类/接口解析、字段解析、方法解析等
5. 初始化(Initialization)
-
执行类构造器
<clinit>()
方法(编译器自动生成) -
按顺序收集:
-
静态变量赋值语句
-
静态代码块
-
三、类加载器
1. 双亲委派模型
启动类加载器(Bootstrap)
↑
扩展类加载器(Extension)
↑
应用程序类加载器(Application)
↑
自定义类加载器(Custom)
工作流程:
-
收到加载请求后先委托父加载器
-
父加载器无法完成时才自己加载
优势:
-
避免重复加载
-
防止核心API被篡改(如自定义java.lang.String)
2. 破坏双亲委派的场景
-
SPI服务加载(如JDBC)
-
OSGi模块化系统
-
热部署实现
四、常见问题
Q1:类加载过程是怎样的?
回答结构:
-
加载 → 验证 → 准备 → 解析 → 初始化
-
各阶段主要工作
-
强调初始化是执行
<clinit>()
方法
Q2:双亲委派模型是什么?
回答要点:
-
类加载器层级结构
-
"向上委托"的工作机制
-
优势(安全性和避免重复加载)
Q3:如何自定义类加载器?
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) {
// 1. 读取类文件字节码
byte[] data = loadClassData(name);
// 2. 调用defineClass
return defineClass(name, data, 0, data.length);
}
}
五、实际应用
-
热部署:通过自定义类加载器实现
-
代码加密:自定义加载器解密字节码
-
模块化隔离:不同模块使用独立类加载器