Java类加载机制是JVM将类的字节码文件加载到内存,并转化为Class对象的过程。主要包括加载、验证、准备、解析、初始化五个阶段。

一、类加载的生命周期

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载

链接(Linking)位于加载(Loading) 和初始化(Initialization) 之间,包含了三个子阶段:验证、准备和解析。核心任务是将加载进来的类的二进制数据,整合到 JVM 的运行时状态中,使其可以被执行。

缓存机制

JVM通过ClassLoader + 类全限定名作为缓存的Key,保证每个类在每个ClassLoader中只完整加载一次,后续使用直接返回缓存的Class对象。类的缓存信息分布在三个区域:元空间、堆、以及JVM内部结构。

类加载的5个阶段(加载、验证、准备、解析、初始化)中,已经加载过的类不会重新执行整个流程。

二、类加载器

层次结构(双亲委派模型)

核心原则:先向上委派,再自己加载

工作流程

  1. 收到加载请求,先向上委派给父加载器

  2. 父加载器继续向上委派,直到顶层的启动类加载器

  3. 从顶层开始尝试加载,能加载则返回

  4. 不能加载则向下传递,让子加载器尝试

  5. 最终都无法加载则抛出异常

各加载器职责

类加载器 加载路径 说明
Bootstrap $JAVA_HOME/jre/lib/rt.jar C++实现,JVM内置
Extension $JAVA_HOME/jre/lib/ext/* 标准扩展API
Application CLASSPATH 加载应用类
Custom 自定义路径 用户自定义

为什么这样设计?

  • 避免重复加载:父加载器加载过的类,子加载器不再重复加载

  • 保证核心库安全:用户无法自定义java.lang.String替换核心类

  • 隔离性:不同类加载器加载的类相互隔离

三、类加载时机

主动引用(会触发初始化)

  1. new实例化对象
    new 实例化对象时,如果该类尚未被加载,会触发该类的完整加载、连接和初始化过程。

  2. 访问静态变量/方法
    访问静态变量/方法是访问类的元数据,而类的元数据在类被加载到内存后才会存在。

  3. 反射调用(Class.forName())
    Class.forName() 默认会触发被加载类的完整初始化(包括静态代码块执行、静态变量赋值)。

  4. 子类初始化,父类先初始化
    当初始化一个类时,如果它的父类还没有被初始化,那么会先递归地初始化父类(一直向上到 Object 类)。

  5. 启动类的main方法
    启动类的 main 方法会触发启动类(即包含 main 方法的那个类)本身的加载

被动引用(不会触发初始化)

// 1. 通过子类引用父类静态变量
System.out.println(Child.parentStatic);  // 只初始化父类

// 2. 数组定义
Parent[] arr = new Parent[10];  // 不初始化Parent

new一个数组时,只是在创建一个容器对象,并没有真正使用到元素类本身的功能。

// 3. 引用静态常量
System.out.println(Class.FINAL_CONST);  // 编译期常量已存入调用类常量池

外部访问编译期常量不需要类加载(值已内联)
内部访问编译期常量也不需要类加载(值也被内联)

编译期常量的值在编译时就“刻”到了所有使用它的代码里(无论内部还是外部),运行时不需要原类。准备阶段赋值是为了支持反射、JNI、序列化等需要访问类元数据的场景,不是给代码内联用的。

四、类加载机制的核心

  • 按需加载:用到时才加载

  • 全权负责:加载的类所依赖的类也由同一加载器加载

  • 缓存机制:加载过的类会缓存

  • 双亲委派:保证安全性和唯一性

  • 沙箱保护:隔离恶意代码,控制权限

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐