JVM 的类加载机制是 Java 虚拟机动态加载、链接和初始化类的核心机制,它遵循严格的流程和规则,确保类的安全性和一致性。以下是详细说明:
类加载的流程
类加载分为 加载(Loading)→ 链接(Linking)→ 初始化(Initialization) 三个阶段:
- 加载(Loading)
- 任务:查找并加载类的二进制字节流(如
.class
文件)。 - 结果:在内存中生成一个代表该类的
Class
对象(方法区中)。 - 数据来源:可以是本地文件、网络、JAR 包等。
- 任务:查找并加载类的二进制字节流(如
- 链接(Linking)
- 验证(Verification):检查字节码是否符合 JVM 规范(如魔数、语法合法性)。
- 准备(Preparation):为类的静态变量分配内存并设置默认初始值(如
int
初始化为 0)。 - 解析(Resolution):将常量池中的符号引用(如类名、方法名)转换为直接引用(内存地址)。
- 初始化(Initialization)
- 执行类的
<clinit>
方法(编译器自动生成,包含静态变量赋值和静态代码块)。 - 触发条件:首次主动使用类时(如
new
对象、访问静态变量/方法、反射调用等)。
- 执行类的
双亲委派模型(Parent Delegation Model)
类加载器通过层级关系协作,确保核心类库的安全性,避免重复加载。
- 层级结构
- Bootstrap ClassLoader(启动类加载器):
加载JAVA_HOME/lib
下的核心类库(如rt.jar
),由 C++ 实现,无父类。 - Extension ClassLoader(扩展类加载器):
加载JAVA_HOME/lib/ext
目录的扩展类。 - Application ClassLoader(应用类加载器):
加载用户类路径(classpath
)下的类,默认的类加载器。 - 自定义 ClassLoader:用户可继承
ClassLoader
实现自定义加载逻辑。
- Bootstrap ClassLoader(启动类加载器):
- 工作流程
示例:加载用户自定义的 java.lang.String
类时,最终会由 Bootstrap ClassLoader
加载核心库的 String
,避免用户篡改。
- 收到加载请求时,优先委派给父类加载器处理。
- 若父类无法完成(在自己的搜索范围内找不到类),子类才会尝试加载。
- 优势
- 避免重复加载,确保类全局唯一。
- 保护核心类库不被自定义类覆盖。
打破双亲委派的场景
某些场景需要绕过双亲委派机制:
- SPI 服务加载(如 JDBC)
Java 核心库(如java.sql.Driver
)由Bootstrap ClassLoader
加载,而 SPI 实现类(如 MySQL 驱动)由应用类加载器加载。此时通过 线程上下文类加载器(Thread Context ClassLoader) 实现父类加载器请求子类加载器完成加载。 - 热部署/热加载
如 Tomcat 为每个 Web 应用提供独立的类加载器,支持应用级类隔离和重新加载。 - 自定义类加载器
用户可重写loadClass()
方法改变委派逻辑。
类初始化的条件
类必须初始化的情况(主动引用):
new
实例对象、读写静态字段(非 final)、调用静态方法。- 反射调用(如
Class.forName("类名")
)。 - 初始化子类时,若父类未初始化,会触发父类初始化。
被动引用示例:
class Parent {
static int value = 10;
static { System.out.println("Parent init!"); }
}
class Child extends Parent {
static { System.out.println("Child init!"); }
}
// 访问 Parent.value 不会初始化 Child 类
类的卸载
- 条件:类的
Class
对象无引用,且对应的类加载器被回收。 - 实现:由 JVM 的垃圾回收机制完成,通常发生在方法区(元空间)内存不足时。
总结
JVM 类加载机制通过 双亲委派模型 和 分阶段加载 确保类的安全加载与隔离,同时支持灵活扩展(如 SPI、热部署)。理解其原理有助于解决类冲突、实现动态加载等高级场景。