正在研究 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)了实例初始化并且有意义。不过,类初始化必须像这样运行:
- CB
<clinit>
实例化一个 CA,它应该触发... - CA
<clinit>
,它实例化一个 CB,它尝试 - CB
<clinit>
再次,这已经在进行中......
JVM 规范在 s5.5 初始化中表示:
- 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();
,因此必须调用构造函数,从而初始化ivar
。 ivar
设置为 IVAR
,但后者只会在构造函数完成后进行初始化。因此,当尝试设置 ivar
时,它将取消装箱 IVAR
的值,此时(因为 CB
尚未完全初始化)为 空
。
关于java - JVM递归类初始化实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64470359/