Java类加载本身不直接引发死锁,但多线程并发初始化时因静态块互依赖、自定义加载器失当或资源顺序混乱,可能陷入JVM级初始化锁闭环,表现为线程卡在<clinit>且状态为RUNNABLE。
Java 类加载过程本身不直接引发死锁,但多线程并发触发类初始化时,若存在静态块互相依赖、自定义加载器设计失当或资源加载顺序混乱,就可能陷入 JVM 级别的类初始化锁等待闭环——表现为线程卡在 <clinit>、状态为 RUNNABLE 却无进展。解决关键不在“加锁”,而在切断隐式依赖链、控制初始化时机、保障加载路径一致。
用 jstack -l <pid> 抓取线程快照,重点关注:
<clinit> 调用,例如 A.<clinit> → B.<clinit> → A.<clinit>
waiting to lock <0x...> 和 locked <0x...> 成对出现且地址循环引用Class.forName、ClassLoader.loadClass 或静态字段访问上System.err.println 输出明确断点静态块不是“兜住异常就安全”,而是“异常后继续访问未就绪类”才致命。必须剥离所有非原子、非确定性操作:
static{} 中读取配置文件、环境变量、系统属性(IO 不稳定)private static class Holder { static final Service INSTANCE = new Service(); },首次访问 Holder.INSTANCE 才触发init() 方法,由调用方控制重试、降级与告警多数“加载失败+疑似死锁”源于加载器破坏双亲委派或锁管理失控:
立即学习“Java免费学习笔记(深入)”;
super(ClassLoader.getSystemClassLoader()),否则父为 null,导致 ClassNotFoundException(如找不到 java.lang.Object)findClass(String),不要重写 loadClass(String)——绕过双亲委派会引发 LinkageError 或 ClassCastException
findClass 中先调用 findLoadedClass(name) 避免重复 define,再读字节码,最后调用 defineClass(name, bytes, 0, bytes.length)
this.getClass().getClassLoader().getResource("xxx"),不硬编码路径,防止因加载器不同导致资源不可见匿名内部类、Lambda 表达式、方法引用在字节码层面可能绑定外部类状态,若在 static 块中创建,会强制触发外部类完整初始化,极易形成闭环:
static{} 中 new 匿名 Runnable、Thread 或使用 ()->{...}
AtomicReference + 懒加载控制SomeClass::method)相对安全,因其不立即触发目标类初始化,但仍有风险,需结合实际字节码验证