1

This is somewhat related to static variable initialization order, but this question doesn't really answer my question.

When I run the following code, I expect undefined behavior or something with infinite recursion to happen:

class A {
    static File test = B.test;
    public static void main(String[] args) {
         System.out.println(A.test);
         System.out.println(B.test);
    }
}
class B {
     static File test = A.test;
}

However, instead I see:

null
null

Why does this happen?

Dancing Bear
  • 55
  • 1
  • 8

1 Answers1

3

The answer comes from JLS section 12.4. We start with 12.4.1:

12.4.1. When Initialization Occurs

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.

  • A static method declared by T is invoked.

  • A static field declared by T is assigned.

  • A static field declared by T is used and the field is not a constant variable (§4.12.4).

When A.main is invoked, the third clause applies, and thus class A (and by extension A.test) must be initialized. The initialization of A.test triggers the fourth clause above, meaning that B must now be initialized. B must now access fields of A, but A has not yet initialized.

We need to now dig into section 12.4.2:

12.4.2. Detailed Initialization Procedure ... For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation. The procedure for initializing C is then as follows:

  1. Synchronize on the initialization lock, LC, for C. This involves waiting until the current thread can acquire LC.

  2. If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this step.

  3. 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.

Step 3 is the one we take here. Class A is already undergoing initialization by the current thread, so we are in a recursive initialization situation. We thus just give up and complete normally right away, so A.test remains null and B.test gets the value of A.test which is null.

Community
  • 1
  • 1
nanofarad
  • 40,330
  • 4
  • 86
  • 117