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

Java 中静态变量不是在类加载完成时就初始化,而是在类的「初始化阶段」才真正赋值,且只执行一次。这个阶段由 JVM 主动触发,按源码顺序执行,父类优先于子类。
准备阶段只是分配内存和设默认值
类加载的「准备阶段」会为所有 static 变量分配内存,并赋予默认值:int 为 0、boolean 为 false、引用类型为 null。但此时你写的 static int x = 10; 中的 = 10 并不执行,x 的值仍是 0。编译期常量(如 public static final String MSG = "ok";)是例外,它会在准备阶段直接写入真实值,甚至不进入后续初始化流程。
初始化阶段才真正执行赋值逻辑
只有当类被「主动使用」时,JVM 才进入初始化阶段,运行一个叫
- 先执行父类的全部静态赋值和静态块(按父类源码顺序)
- 再执行子类的全部静态赋值和静态块(按子类源码顺序)
- 同一类中,谁写在前面,谁先执行;哪怕中间隔着未初始化的字段声明
- 若 static int b = a + 1; 出现在 static int a = 5; 之前,则 b 得到的是 0 + 1 = 1,因为 a 还没走到赋值那步
哪些操作会触发初始化
以下行为属于「主动使用」,会强制 JVM 完成初始化并执行
- 用 new 创建该类第一个实例
- 访问或修改该类的非 final 静态字段(读或写)
- 调用该类的静态方法
- 反射调用 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 大师之旅:从入门到精通的终极指南











