classcircularityerror 是 jvm 类加载时因静态初始化循环依赖抛出的 linkageerror 子类错误;它由 a→b→a 等直接或间接闭环触发,需通过追踪静态引用、启用 -verbose:class 或 jfr 定位,并以延迟初始化、拆分职责或引入协调者修复。

ClassCircularityError 是 JVM 在类加载阶段检测到循环依赖时抛出的错误,属于 LinkageError 的子类。它不是运行时异常(Exception),而是一个**严重错误(Error)**,意味着类结构存在根本性矛盾,JVM 无法安全继续初始化——通常发生在两个或多个类在静态初始化过程中相互依赖,导致加载器陷入死循环。
为什么会触发 ClassCircularityError?
核心原因是:类 A 在静态初始化块(或静态字段初始化)中直接/间接引用了类 B,而类 B 同样在静态初始化中又回引了类 A,且两者都尚未完成初始化。JVM 类加载器为保证线程安全和初始化顺序,会对每个类标记“正在初始化”状态;一旦发现 A 正在初始化时又要加载 B,而 B 又反过来要加载 A,就会立即中断并抛出此错误。
- 常见于静态工具类、单例模式(尤其是早期双重检查锁定未加 volatile)、配置类互相持有对方静态实例
- 不一定是直接引用:A → B → C → A 这样的间接闭环同样会触发
- 与 Spring 的 Bean 循环依赖不同:后者发生在 Spring 容器层面,可通过三级缓存解决;而 ClassCircularityError 发生在 JVM 原生类加载阶段,Spring 无法干预
如何快速定位循环链?
错误堆栈通常只显示“ClassCircularityError: A”,不带详细路径。需结合以下方式排查:
- 查看报错类的静态代码块、static final 字段初始化语句,逐行检查是否调用了其他类的静态方法或字段
- 用 IDE 的“Find Usages”反向追踪:对报错类中所有静态调用点,查其被调用方是否又引用了该类
- 启用 JVM 参数
-XX:+TraceClassLoading或-verbose:class,观察类加载顺序,找出卡住的两个类 - 使用 JFR(Java Flight Recorder)录制启动过程,分析类初始化事件的时间线
常用修复策略
目标是打破静态初始化环节的强依赖,把“必须在类加载时就完成”的逻辑延后或解耦:
-
延迟初始化:将静态字段从
static final Xxx INSTANCE = new Xxx();改为懒汉式,例如用静态内部类或Supplier包装 - 拆分静态职责:把互相依赖的静态逻辑移到非静态方法中,由上层统一协调调用时机
- 引入中间协调者:新增一个不参与循环的类,负责按顺序初始化 A 和 B,避免二者直接互引
- 改用实例依赖(如 Spring 管理):若在 Spring 环境中,优先将类改为普通 Bean,利用容器解决依赖,而非靠静态初始化
预防建议
静态初始化应保持极简,仅做无副作用、无外部依赖的赋值操作:
- 避免在 static 块中调用远程服务、读配置文件、创建复杂对象
- 团队约定:禁止在工具类中通过静态字段持有可能引发循环的其他工具类实例
- CI 阶段加入字节码分析工具(如 ClassGraph、ArchUnit),扫描 static 初始化中的跨类强引用










