1. 引言
本文将深入探讨 Java 中静态变量的初始化过程。这个过程由 Java 虚拟机(JVM)在类加载阶段完成,属于 JVM 类初始化机制的核心部分。
如果你在项目中踩过“静态变量未按预期初始化”的坑,或者对 static
块的执行时机感到模糊,那这篇文章能帮你理清底层逻辑。
2. 类初始化流程
JVM 在加载类时,会按以下三个阶段进行处理:
- ✅ 加载(Loading):通过类加载器将
.class
文件加载进 JVM,生成对应的Class
对象。 - ✅ 链接(Linking):包括验证、准备和解析,其中“准备”阶段会为静态变量分配内存并设置默认值(如
int
为 0,引用类型为null
)。 - ✅ 初始化(Initialization):执行类构造器
<clinit>()
方法,真正执行静态变量赋值和static
块中的代码。
⚠️ 只有当类被“主动使用”时,才会触发初始化。比如调用其 main()
方法、创建实例、访问静态成员等。
最后,JVM 会调用该类的 main
方法启动程序。
3. 静态变量(Class Variable)的初始化
在 Java 中,静态变量也称为 类变量(class variable),它属于类本身,而非某个实例。所有实例共享同一份静态变量。
✅ 类的初始化过程会负责初始化这些静态变量。
❌ 而实例变量(非静态)则由对象创建时通过构造器初始化。
来看一个例子:
public class StaticVariableDemo {
public static int i;
public static int j = 20;
public StaticVariableDemo() {}
}
JVM 执行流程如下:
- 加载
StaticVariableDemo
类,生成Class
对象。 - 准备阶段:为
i
和j
分配内存,初始值均为0
(int
默认值)。 - 初始化阶段:
- 按代码顺序执行静态初始化:
i
保持默认值0
(无显式赋值)。j
被赋值为20
。
- 按代码顺序执行静态初始化:
📌 关键点:静态变量的初始化顺序严格按照代码书写顺序执行。先定义的先初始化。
4. 静态代码块中的变量
静态块(static {}
)也是初始化的一部分,它会在类初始化时执行,常用于复杂逻辑或资源加载。
示例:
public class StaticVariableDemo {
public static int z;
static {
z = 30;
}
public StaticVariableDemo() {}
}
执行顺序:
- 准备阶段:
z
被赋予默认值0
。 - 初始化阶段:
- 执行静态块,将
z
修改为30
。
- 执行静态块,将
✅ 静态块的执行优先于构造器,且只执行一次。
⚠️ 多个静态块按出现顺序依次执行。
这个机制非常适合做单例初始化、配置加载等一次性操作。
5. 静态内部类中的变量
静态嵌套类(static nested class)的静态变量也会在类加载时初始化,但注意:外部类加载并不会自动触发静态内部类的初始化。
只有当首次主动使用静态内部类时,才会触发其初始化。
示例:
public class StaticVariableDemo {
public StaticVariableDemo() {}
static class Nested {
public static String nestedClassStaticVariable = "test";
}
}
行为分析:
- 当
StaticVariableDemo
被加载时,Nested
类并不会立即加载或初始化。 - 只有当代码首次访问
Nested.nestedClassStaticVariable
或创建Nested
实例时,JVM 才会:- 加载
Nested
类 - 初始化其静态变量
nestedClassStaticVariable
为"test"
- 加载
✅ 这种延迟加载特性常被用于实现延迟单例(如 Effective Java 中的 singleton 范式)。
6. 总结
- 静态变量的初始化发生在类的 初始化阶段,由 JVM 的
<clinit>()
方法完成。 - 初始化顺序:按代码顺序,先变量赋值,后静态块执行。
- 静态内部类的静态变量 不会随外部类初始化而初始化,具有延迟加载特性。
- 理解这一机制有助于避免初始化顺序错误、NPE 等隐蔽问题。
如需深入了解底层规范,可查阅 Java Language Specification。
所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-3