You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何单例实例持有者的循环引用不会抛出StackOverflowError?

为什么互相依赖的Holder单例会出现null引用而非栈溢出?

先看这段代码:

static class A {
    private static class InstanceHolder {
        private static final A a = new A();
    }

    private final B b;

    private A() {
        b = B.getInstance();
    }

    static A getInstance() {
        return InstanceHolder.a;
    }
}

static class B {
    private static class InstanceHolder {
        private static final B b = new B();
    }

    private final A a;

    private B() {
        a = A.getInstance();
    }

    static B getInstance() {
        return InstanceHolder.b;
    }
}

public static void main(String[] args) {
    A a = A.getInstance();
    B b = B.getInstance();
    System.out.println(a.b);
    System.out.println(b.a);
}

你原本以为A→B→A的递归构造会触发StackOverflowError,但实际结果是a.b能拿到B的实例,b.a却是null,核心原因在Java类的静态初始化规则:

具体执行流程

  1. main方法调用A.getInstance(),触发A$InstanceHolder静态内部类的初始化——Holder模式就是靠静态内部类的懒加载实现单例。
  2. A$InstanceHolder的静态代码执行new A(),开始构造A实例,进入A的构造方法。
  3. A的构造里调用B.getInstance(),触发B$InstanceHolder的初始化,执行new B()构造B实例。
  4. B的构造里调用A.getInstance(),这时候关键来了:A$InstanceHolder已经处于正在初始化的状态——它的静态变量a还在执行new A()的赋值过程,还没把构造好的A实例赋值给a,此时a的默认值是null。
  5. 根据JVM规则,当一个类正在初始化时,递归访问它的静态成员不会重新触发初始化,而是直接返回该成员的默认初始值(这里就是null)。所以B的构造里a被赋值为null,B实例构造完成后返回给A的构造方法,A的b拿到这个B实例,A实例构造完成后赋值给A$InstanceHolder.a。
  6. 回到main方法调用B.getInstance(),直接返回已经初始化好的B实例,最终输出自然是a.b有效,b.a为null。

为什么没出现栈溢出?

因为在B的构造调用A.getInstance()时,并没有再次触发A实例的构造,只是返回了还没完成赋值的静态变量null,递归到这里就终止了,不会无限循环调用构造方法,也就不会出现StackOverflowError。

内容的提问来源于stack exchange,提问作者Leib Rozenblium

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 15:07:24