静态代码块在类初始化阶段执行且仅一次,不触发类加载而依赖已加载后的主动使用;执行前提为类完成加载并首次被主动使用,触发条件包括new实例、调用静态方法、访问非编译期常量静态字段、class.forname反射加载、子类初始化或jvm启动主类。

静态代码块不触发类加载,但会触发类初始化;它本身不“导致”加载,而是在类已加载、且首次被主动使用进入初始化阶段时才执行——这是理解其影响的关键。
静态代码块执行的前提:类必须已完成加载并进入初始化阶段
类加载(Loading)和类初始化(Initialization)是 JVM 类生命周期中两个分离的阶段。静态代码块属于「初始化阶段」的产物,不是「加载阶段」的行为。也就是说:
- 类可能早已被 ClassLoader 加载进方法区(比如通过 ClassLoader.loadClass("X")),但只要没进入初始化,静态代码块就完全不会运行;
- 只有当 JVM 判定该类“首次被主动使用”,才会启动初始化流程,此时静态代码块才按源码顺序依次执行;
- 加载完成 ≠ 初始化发生。一个类可以长期处于“已加载但未初始化”状态,例如只引用了它的 static final 基本类型常量(如 public static final String NAME = "A";),编译器直接内联值,跳过初始化。
哪些操作真正触发初始化(从而执行静态代码块)
以下六种情形之一发生时,JVM 才会启动类初始化,静态代码块随之执行:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 用 new 创建该类第一个实例;
- 调用该类的任意 静态方法;
- 访问该类的 非编译期常量静态字段(即非 static final 基本类型或字符串字面量);
- 通过 Class.forName("X") 反射加载(注意:ClassLoader.loadClass("X") 不触发);
- 初始化某个子类时(会先确保父类已完成初始化);
- JVM 启动时,直接运行包含 main() 方法的主类。
静态代码块对类加载流程的实际影响
它不改变类加载的时机,但会显著影响初始化行为与稳定性:
- 顺序敏感:静态变量赋值与静态代码块严格按源码从上到下交替执行。若 static String a = init(); 出现在 static { b = "ok"; } 之前,而 init() 内部读取 b,得到的是默认值 null,而非预期值;
- 异常即失败:静态代码块中抛出未捕获异常(包括 ExceptionInInitializerError),该类将永久进入“初始化失败”状态,后续任何主动使用都直接抛出同一异常,无法恢复;
- 阻塞式执行:初始化是单线程同步过程,同一类的多个线程并发触发初始化时,仅一个线程执行静态代码块,其余阻塞等待——这既是安全机制,也可能成为启动瓶颈。
常见误区澄清
几个高频误解需要明确纠正:
- ❌ “静态代码块在类加载时执行” —— 更准确说法是:在类初始化阶段执行,而初始化是加载之后的一个独立阶段;
- ❌ “只要写了 static {},类一加载就跑” —— 若类从未被主动使用(如仅被其他类 import 或作为泛型参数),它可能永远不初始化,静态代码块也永远不会运行;
- ❌ “在静态块里 new 当前类实例没问题” —— 极易引发循环依赖:构造器若间接访问本类尚未初始化完成的静态字段,JVM 将抛出 ExceptionInInitializerError;
- ❌ “static final 字符串也会触发初始化” —— 只有编译期可确定的字面量(如 static final String MSG = "hello";)会被内联,不触发初始化;若值来自方法调用(static final String MSG = getMsg();),则仍会触发。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










