💡 承接上文:《第 2 篇:深入理解 JVM 内存结构与分区示意图》解析了 JVM 的运行时内存结构,重点剖析了方法区、运行时常量池的作用。而这些区域的内容,往往来源于 JVM 对 Class 文件的解析与加载过程。因此,本篇将承接这一点,深入探讨 JVM 的类加载机制、双亲委派模型的设计理念及实际工程中的动态类加载技巧。
一、什么是类加载(Class Loading)?
在 Java 中,类的生命周期包括以下七个阶段:
其中,前五个阶段统称为 类加载过程。这是 JVM 将字节码 .class 文件转换为可执行 Java 类型的关键步骤。
二、类加载的五大阶段详解
1.加载(Loading)
- 查找并加载 .class 文件字节流
- 将二进制数据转为 JVM 结构:Class 对象
- 可能来自于:磁盘、网络、Jar 包、内存等
Class<?> clazz = Class.forName("com.example.User");
2.验证(Verification)
- 确保字节码符合 JVM 规范,防止恶意代码入侵
- 四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证
3.准备(Preparation)
- 分配类变量的内存,并设置默认值(不包含实例变量)
- 不执行任何代码,仅定义变量内存
static int counter = 10;
// counter 为 0,不是 10,初始化还未执行
4.解析(Resolution)
-
将常量池中的符号引用转换为直接引用(地址/偏移量)
-
在某些情况下可延迟解析(如 invokedynamic)
5.初始化(Initialization)
- 执行 () 方法(静态代码块 + 静态变量赋值)
- 由 JVM 保证线程安全和按需初始化(首次主动使用类时触发)
三、类加载器(ClassLoader)体系结构
JVM 中类的加载动作是由 ClassLoader 实现的,核心架构如下:
JVM 中几乎所有的类都是由 Application ClassLoader 加载的。
四、双亲委派模型(Parent Delegation Model)
🔄 原理
在加载类时,先委托父类加载器加载,只有在父加载器无法加载时,才尝试由当前加载器加载。
✅ 优点
- 避免类重复加载(如用户定义的 java.lang.String)
- 保证核心类安全性与一致性
⚠️ 可绕过
有时框架需要打破双亲委派(如 JDBC 驱动、热部署):
Thread.currentThread().setContextClassLoader(customLoader);
五、自定义 ClassLoader 实践
✅ 目的
- 热部署(如 Tomcat)
- 动态代理生成类
- 加载网络、数据库、内存中的类定义
- 实现插件机制
✅ 实例:加载自定义路径下的类文件
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name); // 读取 .class 文件
return defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String className) throws IOException {
String fileName = classPath + className.replace(".", "/") + ".class";
InputStream is = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b;
while ((b = is.read()) != -1) {
baos.write(b);
}
return baos.toByteArray();
}
}
✅ 使用方式
MyClassLoader loader = new MyClassLoader("/custom/path/");
Class<?> clazz = loader.loadClass("com.example.Hello");
Object obj = clazz.newInstance();
六、类加载器的工程应用场景
场景 | 框架 / 系统 | 特性 |
---|---|---|
热部署、动态更新 | Tomcat、JRebel | 定义多个类加载器,模块隔离 |
插件机制 | OSGi、Dubbo SPI | 不同插件互不干扰,动态加载 |
字节码增强与代理 | Spring、CGLIB、Javassist | 基于 ASM 动态生成类 |
JDBC 驱动加载 | JDBC DriverManager | Context ClassLoader 替代默认加载器 |
七、类卸载与内存回收
-
类只有在满足 以下全部条件时才会被 GC 回收:
1.Class 对象不再被引用
2.加载该类的 ClassLoader 无引用
3.加载该类的所有实例不可达
-
常见内存泄漏来源:线程持有 ClassLoader 引用(如 ThreadLocal 未清理)
八、总结与展望
JVM 的类加载机制为 Java 提供了强大的可扩展性与安全隔离能力。在实际工程中,理解类加载器结构和双亲委派模型,有助于我们更好地控制模块加载、实现插件式架构、支持在线热部署等复杂场景。
👉 下一篇文章,我们将进入 JVM 性能调优的重头戏之一 —— 《JVM 垃圾收集器全面对比(Serial、CMS、G1、ZGC)》。我们会全面对比各类 GC(Serial、CMS、G1、ZGC)的设计、使用场景、调优参数,并结合图解讲解内存晋升和分代策略,助你选对 GC、调出极致性能。