Am studying JVM spec/internals, and would like to understand how circularly-referenced recursive class initialization is supposed to happen correctly. Looking at this example:
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();
}
}
Executing this results in:
in CB.init, my svar is 2
in CA.init, my ivar is 1
in CB.init, my svar is 2
Those reflect the instance initializations and make sense. The class inits though, must run like this:
- CB
<clinit>
instantiates a CA, which should trigger... - CA
<clinit>
, which instantiates a CB, which attempts a - CB
<clinit>
again, which is already in-progress...
The JVM spec says under s5.5 Initialization:
- 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.
This implies that at my step 3 above, the JVM shrugs, and goes back to finish step 2. But completing step 2 means calling the constructor <init>
on a new CB instance. How can it do that when class CB has not completed its <clinit>
?
In this case, because the objects are not "doing anything" with the instances of each other they hold, no harm no foul. But how should I be thinking about the behavior and the potential pitfalls here? Thanks.