完整教程:jvm类加载过程

JVM的类加载过程是Java虚拟机将类的.class文件中的二进制数据加载到内存,并进行验证、准备、解析和初始化,最终形成可以被JVM直接使用的Java类型的过程。

该过程至关重要,是Java语言搭建跨平台、动态性安全性的基础。

整个类加载过程可以清晰地分为以下三个大阶段和五个详细步骤:


详细的三个阶段/五个步骤

第一阶段:Loading(加载)

目标​:查找并加载类的二进制数据。

  1. 通过类的全限定名​(如 java.lang.String)来获取其定义的二进制字节流(通常是 .class 文件,但也可以是ZIP包、网络、运行时计算生成等)。
  2. 将这个字节流所代表的静态存储结构转换为方法区(Metaspace/JDK8以前是PermGen)​​ 中的运行时数据结构。
  3. 堆内存中生成一个代表这个类的 java.lang.Class 对象。这个 Class 对象是方法区中数据的访问入口,程序通过它来访问类的类型信息、方法代码等。

注意​:加载阶段与连接阶段的部分内容是交叉进行的,但逻辑上保持先后顺序。

第二阶段:Linking(连接)

连接阶段比较复杂,通常又分为三步:验证、准备和解析。

1. Verification(验证)

目标​:确保被加载的类的正确性和安全性,是JVM安全的关键保障。

  • 文件格式验证​:验证字节流是否符合Class文件格式的规范(如魔数0xCAFEBABE)、版本号是否在当前JVM支持范围内等。
  • 元数据验证否继承了不允许被继承的final类?等等)。就是​:对类的元素材信息进行语义校验,确保其符合Java语言规范(如:这个类是否有父类?
  • 字节码验证​:通过数据流和控制流分析,确定工具语义是合法的、符合逻辑的(如:保证方法体中的类型转换是有效的,不会出现把父类对象转成不相关的子类)。
  • 符号引用验证​:发生在后续的“解析”阶段,验证符号引用能否被正确找到对应的直接引用。
2. Preparation(准备)

目标​:为类变量(静态变量)​​ 分配内存并设置初始值。

  • 此时分配内存的仅包括类变量​(被 static 修饰的变量),​不包括实例变量
  • 设置的初始值通常是数据类型的零值
    • 例如:public static int value = 123; 在准备阶段后,value 的初始值是 0,而不是 123
    • 但如果是常量(static final),准备阶段就会直接赋值为代码中指定的值。例如:public static final int value = 123; 在准备阶段后,value 的值就是 123
3. Resolution(解析)

目标​:将常量池内的符号引用替换为直接引用的过程。

  • 符号引用​:一组符号来描述所引用的目标,与虚拟机内存布局无关。例如 java.lang.Object 就是一个符号引用。
  • 直接引用​:可以是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关。
  • 解析主要针对:​类或接口字段类方法接口方法方法类型方法句柄等符号引用。
第三阶段:Initialization(初始化)

目标​:执行类构造器 <clinit>() 方法的过程,真正开始执行类中定义的Java代码。

  1. 按照源码顺序,为静态变量赋值。
  2. 执行静态代码块。
  • <clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作静态代码块中的语句合并产生的。编译器收集的顺序取决于语句在源文件中出现的顺序。
  • 只有当类被“主动应用”时才会触发初始化,这是类加载过程的结果一步。在此阶段,JVM会确保多线程环境下类的初始化运行被正确地加锁同步。

什么情况下会触发“初始化”?(主动使用)

JVM规范严格规定了有且只有以下6种情况必须立即对类进行“初始化”:

  1. 遇到 new, getstatic, putstatic, invokestatic 这四条字节码指令时
    • 对应代码场景:使用 new 实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)、调用一个类的静态方法。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时
  3. 当初始化一个类时,如果发现其父类还没有进行过初始化,则必须先触发其父类的初始化
  4. 虚拟机启动时,用户应该指定一个要执行的主类(具备main方法的那个类),虚拟机会先初始化这个主类
  5. 当使用JDK7新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
  6. 当一个接口定义了JDK8新加入的默认途径(default)时,如果这个接口的实现类发生了初始化,那该接口要在其之前被初始化

总结与特点

  • 顺序​:加载 -> 验证 -> 准备 -> ​解析​ -> 初始化。​注意​:《Java虚拟机规范》允许解析阶段在某些情况下在初始化之后开始,这是为了支持Java的动态绑定(运行时多态)。
  • 懒加载按需加载(延迟加载),只有在类被“主动使用”时才会初始化。就是​:JVM并不是在启动时就把所有类都加载完,而
  • 双亲委派模型​:类的加载是由类加载器完成的。JVM通过双亲委派模型​(Parents Delegation Model)来组织类加载器之间的关系,从而保证了Java核心类的安全性和唯一性。

简单来说,类加载过程就是JVM将冰冷的字节码文件,转化为内存中一个活跃的、可用的类型的过程,每一步都为确保应用的稳定和安全运行提供了保障

posted @ 2025-09-30 18:14  yxysuanfa  阅读(20)  评论(0)    收藏  举报