类加载机制
→ 返回 Java 基础
→ 相关:JVM(内存结构)· 反射(Class 对象)
类加载过程
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
├────────────────────┤
链接
| 阶段 | 做什么 |
|---|---|
| 加载 | 读取 .class 字节码,生成 Class 对象 |
| 验证 | 校验字节码格式、语义合法性 |
| 准备 | 为静态变量分配内存,赋默认值(int → 0,引用 → null) |
| 解析 | 将符号引用替换为直接引用(内存地址) |
| 初始化 | 执行静态代码块 <clinit>,赋实际值 |
准备阶段
static int x = 10→ x = 0
初始化阶段 → x = 10
触发类初始化的时机(主动引用)
new一个类的实例- 访问类的静态字段或静态方法
- 反射调用(
Class.forName()) - 子类初始化时,父类先初始化
- JVM 启动时的主类(含
main方法的类)
被动引用不触发初始化:
- 通过子类引用父类的静态字段(只初始化父类)
- 数组类型引用(
MyClass[] arr = new MyClass[10])- 引用常量(
static final编译期常量,已内联)
类加载器
Bootstrap ClassLoader(启动类加载器)
└─ Extension ClassLoader(扩展类加载器)
└─ Application ClassLoader(应用类加载器)
└─ 自定义 ClassLoader
| 类加载器 | 负责加载 |
|---|---|
| Bootstrap | JAVA_HOME/lib(rt.jar,String、Object 等核心类) |
| Extension | JAVA_HOME/lib/ext |
| Application | classpath(自己写的代码、第三方 jar) |
Bootstrap 是 C++ 实现的,在 Java 中获取到的是
null。
双亲委派模型
流程:子加载器收到加载请求 → 先委托父加载器加载 → 父加载器找不到时,子加载器才自己加载。
App ClassLoader 收到加载 "com.example.Foo" 请求
→ 委托 Extension ClassLoader
→ 委托 Bootstrap ClassLoader
→ 找不到
← Extension ClassLoader 找不到
← App ClassLoader 自己加载
好处:
- 避免类重复加载
- 保护核心类不被替换(你写一个
java.lang.String,Bootstrap 会优先加载 JDK 的)
打破双亲委派的场景:
- SPI 机制(如 JDBC):Bootstrap 加载接口,但实现类在 classpath,需要用线程上下文类加载器反向委托
- 热部署(Tomcat):每个 webapp 有独立的 ClassLoader,隔离不同应用的类
- OSGi:模块化,类加载关系是网状而非树状
初始化顺序
public class Parent {
static { System.out.println("Parent 静态块"); }
{ System.out.println("Parent 实例块"); }
public Parent() { System.out.println("Parent 构造器"); }
}
public class Child extends Parent {
static { System.out.println("Child 静态块"); }
{ System.out.println("Child 实例块"); }
public Child() { System.out.println("Child 构造器"); }
}执行 new Child() 的输出:
Parent 静态块
Child 静态块
Parent 实例块
Parent 构造器
Child 实例块
Child 构造器
规律:静态 → 实例,父类 → 子类,静态块只执行一次。
自定义类加载器
继承 ClassLoader,重写 findClass() 方法:
public class MyClassLoader extends ClassLoader {
private String classPath;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassBytes(name); // 从自定义位置读取字节码
return defineClass(name, bytes, 0, bytes.length);
}
}重写
findClass而不是loadClass,可以保留双亲委派逻辑。
打破双亲委派需要重写loadClass。