Skip to content

类载入过程 JVM 会做什么

类载入过程

JVM 的类加载过程是指将类的字节码(.class 文件)加载到内存并准备使用的过程,主要分为三个阶段: 1. 加载(Loading):读取字节码到方法区,生成 Class 对象。 2. 链接(Linking): - 验证(Verification):检查字节码合法性。 - 准备(Preparation):为静态变量分配内存并赋默认值。 - 解析(Resolution):将符号引用转为直接引用。 3. 初始化(Initialization):执行静态初始化代码(<clinit>)。

核心点

  • JVM 通过类加载器完成,从加载到初始化,确保类可用。

1. 详细过程分解

(1) 加载(Loading)

  • 做什么
  • 通过类加载器读取 .class 文件的二进制数据。
  • 在方法区创建 Class 对象,表示类的元信息(如字段、方法)。
  • 在堆中存储 Class 实例,作为反射入口。
  • 输入
  • 类的全限定名(如 java.lang.String)。
  • 输出
  • 方法区中的类数据结构。
  • 类加载器
  • 引导类加载器(Bootstrap):加载 JDK 核心类(如 rt.jar)。
  • 扩展类加载器(Extension):加载 ext 目录类。
  • 应用类加载器(Application):加载用户类路径。
  • 双亲委派
  • 子加载器委托父加载器,避免重复加载。
  • 示例
  • Class.forName("java.util.List") 触发。

(2) 链接(Linking)

a. 验证(Verification)
  • 做什么
  • 检查字节码的正确性和安全性。
  • 内容
  • 文件格式:魔数(CAFEBABE)、版本号。
  • 语义:方法调用、变量访问合法。
  • 安全性:无栈溢出、不访问越界。
  • 目的
  • 防止恶意或错误代码运行。
  • 异常
  • VerifyError:验证失败。
b. 准备(Preparation)
  • 做什么
  • 为类的静态变量分配内存,赋默认值。
  • 规则
  • int:0。
  • Objectnull
  • 不包括实例变量(对象创建时处理)。
  • 示例
class Demo {
    static int x = 10;
    static String s;
}
// 准备阶段:x = 0, s = null
c. 解析(Resolution)
  • 做什么
  • 将常量池中的符号引用转为直接引用。
  • 内容
  • 类/接口引用:转为内存地址。
  • 字段引用:转为偏移量。
  • 方法引用:转为方法表索引。
  • 时机
  • 可延迟到使用时(懒解析)。
  • 示例
  • invokevirtual #2 解析为具体方法地址。

(3) 初始化(Initialization)

  • 做什么
  • 执行类的静态初始化代码,由 JVM 生成 <clinit> 方法。
  • 内容
  • 静态变量赋值(如 x = 10)。
  • 静态代码块(static {})。
  • 触发条件
  • new 创建对象。
  • 调用静态方法/字段。
  • Class.forName()
  • 规则
  • 父类先初始化(递归)。
  • 单线程执行,线程安全。
  • 示例
class Demo {
    static int x = 10;
    static { System.out.println("Static block"); }
}
// <clinit>: x = 10, 打印 "Static block"

2. JVM 的具体动作

  • 内存分配
  • 方法区:存储类元信息(字段、方法表)。
  • 堆:存储 Class 对象。
  • 数据结构
  • 方法表:存储方法引用。
  • 常量池:存储字面量和符号引用。
  • 线程安全
  • 类加载器加锁,避免重复加载。
  • 异常处理
  • ClassNotFoundException:类未找到。
  • NoClassDefFoundError:加载失败。

图示

.class 文件 --> 加载 (ClassLoader) --> 方法区 (Class 数据)
                --> 链接 (验证/准备/解析) --> 初始化 (<clinit>)

3. 示例分析

class Parent {
    static int p = 1;
    static { System.out.println("Parent init"); }
}

class Child extends Parent {
    static int c = 2;
    static { System.out.println("Child init"); }
}

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}
  • 过程
  • 加载Parent.class, Child.class 到方法区。
  • 链接
    • 验证:检查字节码。
    • 准备:Parent.p = 0, Child.c = 0
    • 解析:符号引用。
  • 初始化
    • Parent.<clinit>p = 1, 打印 "Parent init"。
    • Child.<clinit>c = 2, 打印 "Child init"。
  • 输出
Parent init
Child init

4. 延伸与面试角度

  • 双亲委派
  • 防止核心类被覆盖。
  • 可破坏(如 OSGi)。
  • 类卸载
  • 类加载器和 Class 对象无引用时 GC。
  • 实际应用
  • Spring:动态加载 Bean。
  • Tomcat:多应用隔离。
  • 面试点
  • 问“过程”时,提三阶段五步骤。
  • 问“初始化”时,提 <clinit>

总结

JVM 类加载过程包括加载、链接(验证、准备、解析)和初始化,将 .class 文件转化为可用的 Class 对象。涉及内存分配、字节码校验和静态初始化,确保类安全可用。面试时,可提双亲委派或举初始化例子,展示理解深度。