Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

  • Java并发

  • Java IO

  • JVM

    • JVM概述
    • JVM基础

    • 类加载机制

      • 类的生命周期
        • 1. 生命周期阶段
        • 2. 加载
        • 3. 验证
        • 4. 准备
        • 5. 解析
        • 6. 初始化
        • 7. 类卸载
        • 8. 生命周期状态图
        • 9. 准备阶段和初始化阶段的细节
        • 10. 什么时候触发初始化
        • 11. 开发中常见问题
          • 11.1 静态初始化失败后会怎样
          • 11.2 为什么不建议静态初始化做重活
        • 12. Tips 快问快答
        • 13. 总结
      • 类加载器
      • 双亲委派模型
      • 类初始化顺序
      • 类加载问题与SPI
    • 运行时内存

    • 垃圾回收

    • 执行引擎与优化

    • 工具与问题排查

    • 调优实践与面试

目录

类的生命周期

类从被 JVM 加载到卸载,会经历一套完整生命周期。理解这个过程,有助于分析静态变量初始化、类加载异常、框架启动、SPI、热部署等问题。

# 1. 生命周期阶段

类的生命周期包括:

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

其中验证、准备、解析合称为连接。

加载 -> 连接 -> 初始化
       ├─ 验证
       ├─ 准备
       └─ 解析

# 2. 加载

加载阶段完成三件事:

  1. 通过类的全限定名获取二进制字节流。
  2. 把字节流转换为方法区中的运行时数据结构。
  3. 在堆中生成代表这个类的 java.lang.Class 对象。

类的字节流可以来自:

  • 本地 class 文件。
  • JAR 包。
  • 网络。
  • 动态生成。
  • 数据库或加密文件。

这也是很多框架能够动态增强类的基础。

# 3. 验证

验证阶段确保 Class 文件合法、安全,不会破坏 JVM。

验证内容包括:

  • 文件格式验证。
  • 元数据验证。
  • 字节码验证。
  • 符号引用验证。

如果 class 文件被篡改或不符合规范,可能在验证阶段失败。

# 4. 准备

准备阶段为类变量分配内存并设置默认初始值。

public static int value = 100;

准备阶段 value 的值是 0,不是 100。赋值为 100 发生在初始化阶段。

特殊情况:

public static final int VALUE = 100;

如果是编译期常量,可能在准备阶段就有常量值。

# 5. 解析

解析阶段把常量池中的符号引用转换为直接引用。

例如:

  • 类或接口解析。
  • 字段解析。
  • 方法解析。
  • 接口方法解析。

解析可以在初始化前完成,也可以在首次使用时按需完成。

# 6. 初始化

初始化阶段执行类构造器 <clinit>()。

<clinit>() 由编译器自动收集:

  • 静态变量赋值语句。
  • 静态代码块。
static int value = 100;

static {
    System.out.println("init");
}

初始化是类加载过程中真正执行业务代码的阶段。

# 7. 类卸载

类卸载需要满足:

  • 该类所有实例都被回收。
  • 该类对应的 Class 对象没有被引用。
  • 加载该类的类加载器可以被回收。

普通应用类通常不会频繁卸载。热部署、脚本引擎、插件系统、应用服务器中更容易遇到类卸载和类加载器泄漏问题。

# 8. 生命周期状态图

                ┌────────┐
                │  加载   │  读取 class 二进制流,生成 Class 对象
                └───┬────┘
                    ▼
              ┌──────────┐
              │   验证     │  文件格式、元数据、字节码、安全性校验
              └────┬─────┘
                   ▼
              ┌──────────┐
              │   准备     │  类变量分配内存,设置默认值
              └────┬─────┘
                   ▼
              ┌──────────┐
              │   解析     │  符号引用 -> 直接引用
              └────┬─────┘
                   ▼
              ┌──────────┐
              │  初始化    │  执行 &lt;clinit>()
              └────┬─────┘
                   ▼
              ┌──────────┐
              │   使用     │
              └────┬─────┘
                   ▼
              ┌──────────┐
              │   卸载     │  Class、实例、类加载器都不再可达
              └──────────┘

注意:解析阶段不一定严格发生在初始化之前,JVM 可以按需解析。学习时把“加载、连接、初始化”作为主流程即可。

# 9. 准备阶段和初始化阶段的细节

准备阶段只给类变量设置默认值:

public class Demo {
    public static int a = 10;
    public static final int B = 20;
    public static String text = "hello";
}

大致理解:

字段 准备阶段 初始化阶段
a 0 10
B 可能直接为 20 通常无需普通赋值
text null "hello"

原因是 static final 基本类型或字符串常量可能在编译期就进入 ConstantValue 属性。

# 10. 什么时候触发初始化

主动使用会触发初始化:

  • new 创建对象。
  • 读写非编译期常量的静态字段。
  • 调用静态方法。
  • 反射调用。
  • 初始化子类前先初始化父类。
  • JVM 启动时初始化主类。

不会触发初始化的常见情况:

public static final int VALUE = 1;

int x = Demo.VALUE; // 编译期常量,通常不会触发 Demo 初始化

通过子类引用父类静态字段,只会初始化真正声明该字段的父类:

class Parent {
    static int value = 1;
}

class Child extends Parent {
}

int x = Child.value; // 初始化 Parent,不一定初始化 Child

# 11. 开发中常见问题

# 11.1 静态初始化失败后会怎样

如果类初始化阶段抛出异常,后续再次使用该类可能出现:

NoClassDefFoundError: Could not initialize class

这类问题常见于静态代码块中读取配置、连接外部服务、初始化复杂资源。

# 11.2 为什么不建议静态初始化做重活

静态初始化发生在类初始化阶段,失败后影响整个类使用,而且异常定位不如普通业务方法直观。

建议:

  • 静态代码块保持简单。
  • 外部资源延迟初始化。
  • 初始化失败要有清晰日志。
  • 不要在静态初始化中做网络调用。

# 12. Tips 快问快答

Q:类加载和类初始化是一回事吗?

A:不是。加载只是把 class 字节流带入 JVM,初始化才会执行静态变量赋值和静态代码块。

Q:准备阶段静态变量为什么不是代码里写的值?

A:准备阶段设置的是类型默认值,显式赋值通常在初始化阶段执行。

Q:接口初始化规则和类一样吗?

A:不完全一样。初始化接口时通常不要求先初始化父接口,只有真正使用到父接口字段时才触发。

Q:类一定会被卸载吗?

A:不一定。只有类实例、Class 对象和类加载器都不可达时才可能卸载。

# 13. 总结

类加载不是简单地把 class 文件读进内存,而是经历加载、验证、准备、解析、初始化等阶段。面试中最容易混淆的是准备和初始化:准备阶段给静态变量默认值,初始化阶段才执行静态赋值和静态代码块。

上次更新: 2026/06/24, 16:03:01
栈帧与方法调用
类加载器

← 栈帧与方法调用 类加载器→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式