java静态块执行虽线程安全,但因jvm隐式类初始化锁机制,若a、b类静态块相互触发对方初始化(如class.forname或访问非编译期常量),多线程并发下会形成循环等待死锁;排查需jstack分析blocked线程栈中调用链,修复应推迟类加载至运行时。

Java静态块执行本身是线程安全的,因为JVM在类初始化阶段自动加锁——每个类对应一把内部锁(即该类的Class对象),确保<clinit></clinit>方法只被一个线程执行。但这不等于“不会死锁”,关键在于静态块里是否触发了其他类的初始化,尤其当多个类相互依赖时,容易形成循环等待。
静态块何时真正执行
静态块不是一加载类就运行,而是在类首次被“主动使用”时才触发初始化,比如:
- 访问非编译期常量的静态字段(如
Config.URL,而Config.VERSION是static final String "1.0"则跳过) - 调用静态方法
- 用
new创建实例 - 反射调用
Class.forName("X")(注意:X.class不触发初始化)
只有这时JVM才会尝试获取该类的初始化锁;若锁已被另一线程持有且正在初始化,当前线程就会阻塞等待。
死锁怎么发生的
典型场景是两个类在静态块中互相触发对方初始化:
- A类静态块里调用
B.class.getAnnotation(...)或Class.forName("B") - B类静态块里又调用了
A.class或访问A的非编译期静态字段 - 线程1先加载A,卡在B初始化上;线程2先加载B,卡在A初始化上
- 两者互相等待,线程状态变为
BLOCKED,且不会抛异常
这种死锁不是你写的synchronized导致的,而是JVM类加载机制隐式形成的等待链。
怎么排查和避免
出现疑似卡顿或启动慢时,可立即用jstack <pid></pid>抓取线程栈,重点看:
- 是否有多个线程停在
java.lang.Class.forName0(Native Method)或java.lang.ClassLoader.loadClass - 它们的栈帧是否层层嵌套着不同类的
<clinit></clinit> - 是否存在A→B→A这样的调用闭环
修复方向很明确:
- 把跨类静态依赖推迟到运行时(比如放在普通方法里,而非
static{}中) - 确认
static final字段是否真是编译期常量——含System.getProperty()、new String()、UUID.randomUUID()等都会破坏内联,强制初始化 - 避免在静态块中做耗时操作(如网络请求、文件读取),更不能同步调用其他可能未初始化的类
类锁和你手动加的锁不是一回事
别混淆JVM内置的类初始化锁和synchronized(X.class):
- 前者由JVM自动管理,只在
<clinit></clinit>执行期间生效,初始化完成后即释放 - 后者是你自己写的同步块,用于保护自定义的延迟初始化逻辑(比如懒加载配置Map),它和JVM初始化流程无关
- 如果在
synchronized(X.class)块里又触发了Y类初始化,而Y反过来依赖X,同样会死锁
也就是说,锁对象虽都是X.class,但作用阶段和目的完全不同。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











