When a Java class is referenced and initialized, it has to go through the loading and linking first. Once the loading and linking complete successfully. The class will be initialized. The static variables and constant variables will be initialized during this process. Once the class is initialized, it is ready for use.
If when class A is initialized and it is referencing a class B, the class B will also get initialized. But what will happen if class B is referencing class A as well? This is called recursive class initialization. Will there be a deadlock if this case happens? No, JVM will take care of this situation for you. In this post, we will discuss the recursive class initialization in Java.
For each class or interface C, there is a unique initialization lock LC for C. According to JLS 8.0 section 12.4.2, a class or interface C initialization involves below steps:
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.
There are also some other steps involved, but they are quite self-explained. Here we focus on the step 3 which is most difficult for developers to understand. Below we will use an example to demonstrate step 3.
Here we define three classes StaticFinalTest1, StaticFinalTest2 and Test. Test is the main entry of the program.
StaticFinalTest1.java
public class StaticFinalTest1 { static final String[] staticFinal; static final boolean mode; static{ staticFinal = StaticFinalTest2.TLS_v11; //Reference to StaticFinal2 System.out.println("StaticFinalTest1 : staticFinal = "+StaticFinalTest2.TLS_v11); mode = true; } public static String[] getStaticFinal(){ return staticFinal; } public static boolean isMode(){ return mode; } }
StaticFinalTest2.java
public class StaticFinalTest2 { static final StaticFinalTest2 NONE = new StaticFinalTest2(-1, "NONE"); static{ System.out.println("StaticFinalTest2 : NONE = "+NONE); } static final boolean isMode = StaticFinalTest1.isMode() ? true: false; static{ System.out.println("StaticFinalTest2 : isMode = "+isMode); } static final String[] TLS_v11 = {"TLSv1.1"}; static{ System.out.println("StaticFinalTest2 : TLS_v11 = "+TLS_v11); } public final int v; final String name; private StaticFinalTest2(int v, String name) { this.v = v; this.name = name; System.out.println("StaticFinalTest2 : CONSTRUCTOR"); } }
Test.java
public class Test { public static void main(String[] args){ System.out.println(StaticFinalTest2.NONE); //LINE 3 System.out.println("Static final : "+StaticFinalTest1.getStaticFinal()); //LINE 4 } }
When running the Test.java, you will see the output as :
StaticFinalTest2 : CONSTRUCTOR StaticFinalTest2 : NONE = StaticFinalTest2@284d023f StaticFinalTest1 : staticFinal = null StaticFinalTest2 : isMode = true StaticFinalTest2 : TLS_v11 = [Ljava.lang.String;@6e3f6813 StaticFinalTest2@284d023f Static final : null
See line 3 of the output. Why is staticFinal null? Isn't it StaticFinalTest2.TLS_v11 which is {"TLSv1.1"}? Let's get to know why it's null now.
- When in Test.java, we first print StaticFinalTest2.NONE, at this point, StaticFinalTest2 will be initialized, hence the current thread will acquire the lock for StaticFinalTest2.
- In StaticFinalTest2, the static variables will be initialized in textual order, so static variable NONE will be initialized by creating a StaticFinalTest2 object, hence we see output line 1 and line 2.
- Then boolean variable isMode is initialized, but wait, the right side of the assignment is an expression. And the expression references StaticFinalTest1, so StaticFinalTest1 will be initialized now.
- In StaticFinalTest1, staticFinal is initialized in the static block and it is assigned with StaticFinalTest2.TLS_v11. So it tries to initialize StaticFinalTest2. However, currently StaticFinalTest2 is being initialized. So it just continues and initialized StaticFinalTest1.
- staticFinal references StaticFinalTest2.TLS_v11, but at the moment, StaticFinalTest2.TLS_v11 is not initialized yet. So the default value for StaticFinalTest2.TLS_v11 will be assigned to staticFinal, the default value is null. You see output line 3 now.
- Then StaticFinal1 will continue be initialized and the mode will be set to true. Then you see output line 4
- The rest initialization process continue in textual order
Now what if you comment line 3 in Test.java? The output is :
StaticFinalTest2 : CONSTRUCTOR StaticFinalTest2 : NONE = StaticFinalTest2@7d21e8ed StaticFinalTest2 : isMode = false StaticFinalTest2 : TLS_v11 = [Ljava.lang.String;@3f9935cb StaticFinalTest1 : staticFinal = [Ljava.lang.String;@3f9935cb Static final : [Ljava.lang.String;@3f9935cb
Below is the steps for initialization:
- When in Test.java, we first print the StaticFinalTest1.getStaticFinal(). At this point, StaticFinalTest1 is being initialized and the current thread will acquire the licj for StaticFinalTest1
- In StaticFinalTest1, staticFinal is initialized to StaticFinalTest2.TLS_V11, so StaticFinalTest2 will be initialized now.
- In StaticFinalTest2, the static variable NONE is initialized by creating a StaticFinalTest2 object, hence you see output line 1 and line 2
- Then when initializing boolean variable isMode, it refers to StaticFinalTest1 again. Now we have a recursive request for initialization of StaticFinalTest1, so it will just continue to initialize StaticFinalTest2
- The value of mode in StaticFinalTest1 will be false at the moment since it's not initialized yet. That''s why you see output line 3
- StaticFinalTest2 will continue to be initialized and StaticFinalTest2.TLS_v11 will be initialized to {"TLSv1.1"}. Then in StaticFinalTest1, the staticFinal will be assigned the value of {"TLSv1.1"}. Hence you see output line 5
- The rest of the initialization are completed in textual order
Hope this post will help you understand how recursive class initialization is handled in Java.