静态初始化中函数调用会死锁,因JVM用CLINIT锁串行执行<clinit>,若A类静态块调B.doInit()触发B初始化,而B又依赖A未完成初始化的字段,便形成A→B→A闭环等待。

类静态初始化阶段调用函数(尤其是跨类方法或触发其他类加载的操作),容易引发 JVM 级别的初始化死锁,根本原因不是函数本身有问题,而是该调用在类尚未完成初始化时,反向依赖了另一个也正在初始化的类,形成 A → B → A 的闭环等待。解决核心是:不让初始化逻辑产生跨类阻塞式依赖。
静态初始化中函数调用为何会死锁
JVM 对每个类加了一个隐式 initialization lock(CLINIT 锁),保证 <clinit> 方法串行执行。但若:
- A 类静态块里调用了
B.doInit() - 而
B.doInit()是static方法,其首次执行会触发 B 类初始化 - B 类初始化过程中又访问了 A 类尚未赋值完成的
static final字段或调用了 A 的静态方法
→ 两个线程分别卡在 A. 等 B 初始化完成、B. 等 A 初始化完成,互相 BLOCKED。
常见高危调用包括:
-
Class.forName("X") - 访问另一个类的
public static final字段(非编译期常量) - 调用其他类的
public static方法(尤其内部含日志、配置、反射等副作用) - 枚举构造器中调用 Service 类静态方法
- 接口
static方法里引用了未初始化类的字段
如何定位具体哪一行函数调用导致死锁
不用猜,靠线程快照直接看:
- 运行时加
-XX:+PrintConcurrentLocks或用jstack -l <pid> - 搜索关键词:
BLOCKED (on object monitor)、at java.lang.Class.forName、at <clinit>、at com.xxx.A.<clinit> - 找出两个及以上线程堆栈中反复出现的类名交叉(如线程1停在
A.<clinit>等B.class,线程2停在B.<clinit>等A.class) - 再结合源码,确认这些类的 static 块或 static 字段初始化表达式中,哪一行调用了外部函数
替代方案:把函数调用移出静态初始化阶段
目标是让函数真正执行时,相关类已完全初始化完毕:
-
改用 Holder 模式(推荐)
把函数调用包裹进一个静态内部类,利用“内部类首次使用才初始化”的特性延迟执行:public class A { private static final Supplier<String> value = () -> computeValue(); public static String getValue() { return value.get(); } // 第一次调用才执行 computeValue() private static String computeValue() { // 这里可安全调用 B.doSomething(),B 已初始化完成 return B.doSomething(); } } -
改为懒加载 + 显式初始化方法
不在 static 块里做任何跨类操作,提供一个init()方法由上层统一触发:public class A { private static String data; static { /* 只做本类纯本地初始化 */ } public static void init() { if (data == null) { data = B.fetchConfig(); // 此时 B 已就绪 } } } -
拆分强耦合逻辑到独立配置类
若 A 和 B 相互需要对方的静态数据,提取出第三类 C,由 C 统一加载并提供不可变实例:public class ConfigHolder { public static final A aInstance = new A(); public static final B bInstance = new B(); static { aInstance.setB(bInstance); bInstance.setA(aInstance); } }
预防:静态块里只做三件事
写 static 块时,默念这三条红线:
- ✅ 赋值编译期常量(
static final int X = 42;) - ✅ 初始化本类纯内存对象(
new HashMap<>()、Arrays.asList(...)) - ✅ 调用本类
private static工具方法(无外部依赖) - ❌ 不调用任何其他类的
public static方法 - ❌ 不访问其他类的
static字段(除非确定是static final且值为字面量) - ❌ 不触发 ClassLoader 行为(
Class.forName、.class引用、ClassLoader.loadClass)
不复杂但容易忽略
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xinjizixun/123765.html