java静态变量在准备阶段仅分配内存并设默认值,真正赋值发生在初始化阶段,由jvm主动触发、只执行一次,按源码顺序执行且父类优先于子类。

Java 静态变量不是在类“加载完成”那一刻就初始化的,而是在类加载流程的「初始化阶段」才真正赋值,且只执行一次。这个阶段由 JVM 主动触发,按源码顺序执行,父类优先于子类。
准备阶段:分配内存 + 设默认值
当类进入类加载的「准备阶段」时,JVM 会为所有 static 变量分配内存,并赋予默认值:
- 基本类型:int → 0,boolean → false,long → 0L 等
- 引用类型:全部设为 null
- 例外情况:public static final 的编译期常量(如 public static final String MSG = "ok";)会在准备阶段直接写入真实值,甚至跳过后续初始化
初始化阶段:执行显式赋值和静态块
只有当类被「主动使用」时,JVM 才进入初始化阶段,运行一个叫 <clinit></clinit> 的特殊方法。它把所有静态变量赋值语句和 static{} 块按源码从上到下合并执行:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 先执行父类中所有静态变量赋值 + 静态块(严格按父类源码顺序)
- 再执行子类中所有静态变量赋值 + 静态块(同样按子类源码顺序)
- 同一类中,谁写在前面谁先执行;哪怕中间隔着未赋值的字段声明,也不会提前读取其值
- 例如:
static int b = a + 1;出现在static int a = 5;之前,则 b 得到的是0 + 1 = 1
哪些操作会触发初始化
以下行为属于「主动使用」,会强制 JVM 执行 <clinit></clinit>:
- 用
new创建该类第一个实例 - 读取或修改该类的非 final 静态字段(如
MyClass.count++) - 调用该类的静态方法
- 通过
Class.forName("ClassName")反射加载(默认initialize = true) - 初始化子类时,若父类尚未初始化,则先触发父类初始化
这些操作不会触发:引用 ClassName.class、创建数组 MyClass[] arr、访问 public static final 编译期常量。
常见陷阱与注意事项
静态初始化看似简单,但容易出错:
- 避免跨类依赖尚未初始化的静态变量,尤其父子类之间易形成隐式依赖
- 静态数组必须显式初始化(如
private static String[] cache = new String[100];),否则仍是 null - 含方法调用的赋值(如
static String cfg = loadFromDB();)会在初始化时真实执行,可能引发异常或阻塞 - 静态块中不能用
this或访问实例成员,也不能new当前类实例(易造成循环初始化) - 同一个类名被不同 ClassLoader 加载,会产生彼此隔离的静态状态
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










