Skip to content

类的加载过程

更新: 1/23/2026 字数: 0 字

1、从磁盘上的 .java 源代码到内存中鲜活的 Object,是一个跨越编译期和运行期的过程。我们可以将其拆解为四个核心阶段

阶段一:编译期(Source to Bytecode)

在项目启动之前,首先要将人类可读的代码转化为 JVM 能理解的指令。

词法/语法分析: javac 编译器检查你的代码是否符合 Java 语法。

符号表填充: 记录类名、变量名、方法名。

生成字节码: 最终产出 .class 文件。这个文件包含了常量池、方法表、字段表以及最重要的指令流。

阶段二:类加载过程(Loading & Linking)

当你执行 java -jar 启动项目时,JVM 会寻找主类(Main Class)并触发加载。

  1. 加载 (Loading) 通过**类加载器(ClassLoader)**将 .class 文件的二进制流读入内存,并在 方法区(Method Area/Metaspace) 创建对应的 Class 对象。

  2. 链接 (Linking) 这个阶段分三步,确保代码能安全运行:

验证 (Verification): 检查字节码是否合法,会不会破坏 JVM 安全(比如有没有越界跳转)。

准备 (Preparation): 为静态变量分配内存并赋初始零值(如 int 设为 0,Object 设为 null)。注意此时还没执行你的赋值逻辑。

解析 (Resolution): 将常量池内的符号引用(比如方法名字符串)替换为直接引用(内存地址指针)。

  1. 初始化 (Initialization) 这是你上一条提问中提到的关键环节。JVM 执行 <clinit> 方法,真正开始运行你的静态代码块(static {})和静态变量赋值逻辑。此处有 JVM 锁保证线程安全。

阶段三:运行时内存布局(Runtime Data Areas)

类加载完成后,JVM 的内存空间已经各就各位,准备为对象分配地盘:

  • 方法区 (Metaspace): 存放类的结构信息(常量池、字段、方法数据)。

  • 堆 (Heap): 绝大多数对象“出生”的地方,也是垃圾回收的主战场。

  • 栈 (Stack): 每个线程私有。每调用一个方法就会压入一个“栈帧”,存放局部变量。

  • 程序计数器 (PC): 记录当前线程执行到哪一行字节码了。

阶段四:创建对象(Object Instantiation)

当代码执行到 new User() 时,真正的“造物”过程开始:

  • 类加载检查: JVM 先看 User 类加载过没有。如果没有,重复阶段二。

  • 分配内存:

根据类信息确定对象大小。

在堆中划出一块空间。

并发安全: 使用 TLAB (Thread Local Allocation Buffer),每个线程先在自己私有的缓冲区分配,避免多线程抢地盘。

  • 初始化零值: 将内存空间初始化为零(保证了对象实例变量不赋值也能用,因为有默认值)。

  • 设置对象头: 在对象头上打上标记(Mark Word),记录它是哪个类的实例、哈希码、GC 分代年龄、锁状态等。

  • 执行构造方法 (<init>): 按照你的代码逻辑,执行构造函数,进行真正的属性赋值。

总结:从磁盘到内存的链条

  • 磁盘: User.class 文件。

  • 方法区: 加载类的“模版”信息。

  • 栈: 存放引用(Reference),就像遥控器。

  • 堆: 存放真正的对象实例,就像电视机。

举个例子: > 栈里的 User user 只是一个 4 字节或 8 字节的地址,它指向堆中那块巨大的 User 对象内存。

2、简单的 User 类作为示例,然后看它在 JVM 运行时的各个内存区域是如何分布的

java
public class User {
    // 静态变量(类变量)
    public static String group = "Admin"; 
    
    // 实例变量
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
        // 局部变量
        int temp = 10;
        System.out.println("Hello, " + name);
    }
}

// 启动代码
User user = new User("Alice", 25);
user.sayHello();

视觉化布局描述

如果我们用一种更形象的方式来描述 User user = new User("Alice", 25) 这一行代码:

1、在栈(Stack)里: 你会看到一个名为 user 的小格子,里面存着一串十六进制数字(例如 0x1234),这就像是一把遥控器。

2、在堆(Heap)里: 在地址 0x1234 的位置,躺着一块肥沃的土地。

  • 对象头:标记着它是 User 类,还有它的哈希码、锁状态。

  • 实例数据:这里存着 age = 25。至于 name,它又是一个“子遥控器”,指向堆中另一块存着 "Alice" 字符数组的区域。

3、在元空间(Metaspace)里: 这里是 “图纸库”。

  • 它记录了 User 类有哪些方法(sayHello)、哪些字段。

这里的 group 变量就像是一个公共告示牌,所有 User 实例都能看到它写着 "Admin"。

总结

栈是“动作”:负责方法调用和局部变量(快、私有)。

堆是“工厂”:负责生产和存放对象实体(大、共享)。

元空间是“规格”:负责存放类的模版信息(稳、持久)

text
绘制了一张详细的 JVM 内存布局图。这张图展示了执行 User user = new User("Alice", 25); 时,数据是如何在各区域分布并相互连接的;

线程私有区域 (Thread Local)               |             线程共享区域 (Shared)
==========================================    |    ==========================================
                                              |
      【虚拟机栈 Stack】 (线程A)               |            【堆 Heap】 (对象实例)
   +------------------------------+           |    +---------------------------------------+
   |   sayHello() 方法栈帧         |           |    |                                       |
   | +--------------------------+ |           |    |   +-------------------------------+   |
   | | 局部变量表:               | |           |    |   |       User 对象实例 (0x1234)   |   |
   | | [0] this (指向堆0x1234) --|-------------|--->| +---------------------------+   |   |
   | | [1] temp = 10            | |           |    | | [对象头] Mark Word / 类指针  |---|---|--+
   | +--------------------------+ |           |    | +---------------------------+   |   |  |
   +------------------------------+           |    | | [实例数据] age = 25         |   |   |  |
   |   main() 方法栈帧             |           |    | | [实例数据] name (指向"Alice")|---|---|--+
   | +--------------------------+ |           |    | +---------------------------+   |   |  |  |
   | | user变量 (指向堆0x1234)---|-------------|--->|                               |   |  |  |
   | +--------------------------+ |           |    +---------------------------------------+  |
   +------------------------------+           |             |                                 |
                                              |             v                                 |
      【程序计数器 PC】                        |    +---------------------------------------+  |
   +------------------------------+           |    |     String "Alice" (对象/数组)         |  |
   |  当前指令: 0x0021 (sayHello)  |           |    +---------------------------------------+  |
   +------------------------------+           |                                               |
                                              |                                               |
==========================================    |    ==========================================
                                              |
                                              |          【元空间 Metaspace】 (本地内存)
                                              |    +---------------------------------------+
                                              |    |                                       |
                                              |    |   +-------------------------------+   |
                                              |    |   |       User 类元信息 (模版)     |<--+
                                              |    |   +-------------------------------+   |
                                              |    |   | 方法数据 (sayHello代码)        |   |
                                              |    |   | 静态变量: group = "Admin"      |   |
                                              |    |   | 常量池: "Hello, "              |   |
                                              |    |   +-------------------------------+   |
                                              |    |                                       |
                                              |    +---------------------------------------+