java - JVM递归类初始化实现

标签 java jvm language-lawyer

正在研究 JVM 规范/内部结构,并且想了解循环引用的递归类初始化应该如何正确发生。看这个例子:

 class CA extends Object {
    public final int ivar = 1;
    public static CB other = new CB(); 
    public CA() {
        System.out.println("in CA.init, my ivar is " + this.ivar); 
    }   
}
class CB extends Object {
    public final int ivar = 2;
    public static CA other = new CA();  
    public CB() {
        System.out.println("in CB.init, my ivar is " + this.ivar); 
    }
    
    public static void main(String[] args) {
        CB cb = new CB();  
    }
}

执行此结果:

in CB.init, my svar is 2
in CA.init, my ivar is 1
in CB.init, my svar is 2

这些反射(reflect)了实例初始化并且有意义。不过,初始化必须像这样运行:

  1. CB <clinit>实例化一个 CA,它应该触发...
  2. CA <clinit> ,它实例化一个 CB,它尝试
  3. CB <clinit>再次,这已经在进行中......

JVM 规范在 s5.5 初始化中表示:

  1. If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally.

这意味着在上面的第 3 步中,JVM 耸耸肩,然后返回完成第 2 步。但是完成第 2 步意味着调用构造函数 <init>在新的 CB 实例上。当CB类还没有完成它的<clinit>时,它怎么能做到这一点呢? ?

在这种情况下,因为对象没有对它们所持有的彼此的实例“做任何事情”,所以没有伤害也没有犯规。但我应该如何考虑这里的行为和潜在的陷阱?谢谢。

最佳答案

这只有效,因为这些是静态字段(其他),如果您删除该修饰符 - 您将得到一个StackOverflow(因为对于实例字段,初始化被移至构造函数)。在我看来,如果我向您展示编译器实际上在做什么,事情可能会变得显而易见?

static class CA extends Object {

    public final int ivar = 1;
    public static CB other;

    static {
        System.out.println("running CA static block");
        other = new CB();
        System.out.println("CB done");
    }

    public CA() {
        System.out.println("in CA.init, my ivar is " + ivar);
    }
}

static class CB extends Object {

    public final int ivar = 2;
    public static CA other;

    static {
        System.out.println("running CB static block");
        other = new CA();
        System.out.println("CA done");
    }

    public CB() {
        System.out.println("in CB.init, my ivar is " + ivar);
    }


}

编辑

在类完全初始化之前搞乱实例方法的调用确实很危险。您可能会踩到意想不到的东西:

 static class CB {

    private static final CB ONLY = new CB();

    private static final Integer IVAR = 42;
    public final int ivar = IVAR;

}

public static void main(String[] args) {
    System.out.println(CB.ONLY.ivar);
}

这会抛出一个NullPointerException。为什么?您可以自己反编译并查看,但用相当简单的话来说:

  • ivar 在构造函数中通过读取 IVAR 变量进行初始化

  • 静态按照它们在代码中出现的顺序执行

因此,首先执行private static final CB ONLY = new CB();,因此必须调用构造函数,从而初始化ivarivar 设置为 IVAR,但后者只会在构造函数完成后进行初始化。因此,当尝试设置 ivar 时,它将取消装箱 IVAR 的值,此时(因为 CB 尚未完全初始化)为

关于java - JVM递归类初始化实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64470359/

相关文章:

c++ - 在这种情况下,关键字 "typename"和 "template"都不需要吗?

javascript - ECMAScript 规范是否允许 Array 为 "superclassable"?

java - Tomcat - 自定义 session Cookie 处理

java - 是否有适用于 Jython 或 JRuby 的框架,或者您可以在 JVM 上运行 py 或 ruby​​ 应用程序吗?

jvm - 使用 Clojure/JVM 的守护进程

java - 奇怪的 JVM 线程挂起 - 有建议进行故障排除吗?

c++ - 为什么允许将派生类的方法静态转换为基类的方法?

java - 元素 "mvc"的前缀 "mvc:resources"未绑定(bind)

java - 在 Java 中是否遵循 true?

java - Google App Engine 的 .net 包装器?