Java类静态初始化因JVM隐式加锁机制,在多线程下若A、B类static块相互调用Class.forName(),会因争夺彼此类锁形成循环等待死锁;排查需jstack分析BLOCKED线程栈,修复应推迟类加载至运行时。

理解静态初始化与线程死锁的关联
Java 中类的静态初始化块(static {})在类首次主动使用时由 JVM 触发,且保证**单线程执行、全局同步**。若多个类在静态初始化阶段相互依赖(如 A 的 static 块中引用 B.class,B 的 static 块中又引用 A.class),而两个线程分别抢先加载这两个类,就可能陷入“类加载锁等待”循环——本质是 JVM 内部对 ClassLoader 加锁 + 初始化状态检查造成的隐式死锁,不是传统 synchronized 锁竞争。
用代码块复现典型死锁场景
以下是最小可复现示例,无需外部依赖:
// A.java
public class A {
static {
System.out.println("A 正在初始化...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
Class.forName("B"); // 触发 B 初始化
System.out.println("A 初始化完成");
}
}
// B.java
public class B {
static {
System.out.println("B 正在初始化...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
Class.forName("A"); // 触发 A 初始化
System.out.println("B 初始化完成");
}
}
启动两个线程并发执行 Class.forName("A") 和 Class.forName("B"),大概率卡住。JVM 线程栈会显示类似:
"Thread-0" #12 prio=5 ... waiting to lock <0x0000000712345678> (a java.lang.Class for A)<br> "Thread-1" #13 prio=5 ... waiting to lock <0x0000000712345679> (a java.lang.Class for B)
实战排查三步法
1. 捕获线程快照
运行时加 JVM 参数:-XX:+PrintConcurrentLocks -XX:+PrintGCDetails,或直接用 jstack <pid>。重点关注 java.lang.ClassLoader.loadClass 和 java.lang.Class.forName 调用链中的 BLOCKED 状态。
2. 定位静态依赖环
- 反编译可疑类:javap -c A | grep "ldc.*B" 查看字节码是否硬编码引用其他类
- 检查所有 static {} 块:是否调用 Class.forName、访问其他类的 static final 字段、或 new 了依赖类实例
- 使用 jdeps --list-deps --recursive your.jar 辅助分析静态依赖图
3. 验证修复方案
- ✅ 推迟初始化:把 Class.forName("B") 移到普通方法中,首次调用时再加载
- ✅ 拆分静态逻辑:将耗时操作(如反射、IO)移出 static 块,改用懒汉式 Holder 类
- ❌ 避免在 static 块中调用可能触发另一类初始化的方法(包括日志框架的 static logger 获取)
预防性编码习惯
静态初始化块应只做三件事:
- 赋值简单常量(static final int MAX = 100;)
- 构建不可变基础对象(static final List<string> EMPTY = Collections.emptyList();</string>)
- 初始化线程安全的本地缓存(如 static final Map<String, String> CONFIG = new ConcurrentHashMap<>();)
任何涉及类加载、反射、网络、文件、日志、Spring Bean 查找的操作,一律推迟到运行时。

















