Most Java developers, when asked where a program starts, will give a confident, one-word answer: main. It’s one of the first things we learn. The public static void main(String[] args) method is the designated entry point for the Java Virtual Machine (JVM). But is it truly the first thing that runs?
Let’s put that assumption to the test. Take a look at the following code and predict its output. No compiling—just a gut reaction.
class Base {
static {
m1();
System.out.println("First static block");
}
public static void main(String[] args) {
m1();
System.out.println("Main method");
}
public static void m1() {
System.out.println(j);
}
static {
System.out.println("Second static block");
}
static int j = 20;
}
If you predicted anything starting with 20, or that the main method would execute first, prepare to have your assumptions challenged. The actual output is 0, First static block, Second static block, 20, Main method. Let's break down why. This article will unravel the mystery by revealing three core truths about Java's static control flow that challenge our common assumptions.
--------------------------------------------------------------------------------
1. Surprise! The main Method is the Last Thing to Run
The biggest misconception about static control flow is that the main method is the starting point. Contrary to this popular belief, when a class contains static members, the main method is actually the final step in a much longer initialization sequence.
The JVM must first prepare the class by loading and initializing all its static components before it can ever call main. This means that by the time main executes, a series of other operations has already been completed.
"most of the people are going to feel sir main method is the starting point of our program execution but strictly speaking in static control flow main method is the last thing which is going to be executed"
--------------------------------------------------------------------------------
2. The Real Execution Order: A Three-Step Process
So, if main isn't the beginning, what is? The JVM follows a strict, predictable three-step process to initialize a class and its static members. Understanding this sequence is the key to predicting the output of our quiz.
- Step 1: Identification of static members from top to bottom. First, the JVM performs a complete scan of the class from top to bottom, identifying every static member (variables, methods, and blocks). During this phase, any static variables are assigned their JVM-provided default values (
0forint,nullfor objects). At this point, the variable enters a "Read Indirectly, Write Only" (RIWO) state, a concept we'll explore in detail in the next section. - Step 2: Execution of static variable assignments and static blocks from top to bottom. Next, the JVM makes a second pass through the class, again from top to bottom. This time, it executes two things in the order they appear: the explicit assignments for static variables (e.g.,
j = 20) and the code inside anystaticblocks. - Step 3: Execution of the
mainmethod. Only after the identification and execution steps are fully complete does the JVM finally invoke themainmethod.
With this three-step process, the output of the quiz code becomes clear. Let's trace the execution:
- Step 1: Identification (Top to Bottom)
static { m1(); ... }is identified.public static void main(...)is identified.public static void m1()is identified.static { ... }is identified.static int jis identified. The JVM assigns its default value:j = 0. The variablejis now in its RIWO state.
- Step 2: Execution (Top to Bottom)
- First static block executes:
m1()is called.m1()prints the value ofj. Sincejis still in its RIWO state, this indirect read prints its default value. Output:0System.out.println("First static block")runs. Output:First static block
- Second static block executes:
System.out.println("Second static block")runs. Output:Second static block
- Static variable assignment executes:
j = 20is assigned.jis now fully initialized and no longer in the RIWO state.
- First static block executes:
- Step 3: Main Method Execution
- The
mainmethod is finally invoked. m1()is called. It prints the current value ofj. Output:20System.out.println("Main method")runs. Output:Main method
- The
--------------------------------------------------------------------------------
3. The "Illegal Forward Reference" and the State of RIWO
This strict ordering leads to a subtle but critical compiler rule regarding forward references. Sometimes you can access a static variable before its declaration, and other times you get a compile-time error. This behavior is governed by the variable's state and a compile-time safety feature.
To understand this, let's sharpen our definitions:
- Read Indirectly Write Only (RIWO) State: The state of a static variable after the JVM has identified it (Step 1) but before its explicit value has been assigned (Step 2).
- Direct Read: Accessing a variable within a static block before the line where it is declared and initialized.
- Indirect Read: Accessing that same variable from a method that is called from the static block.
The rule that connects these concepts is simple but critical: if a variable is in the RIWO state, you cannot perform a Direct Read, but you can perform an Indirect Read.
The reason for this is a compiler safeguard. A direct read of a variable before its initializer has run is a likely logic error. The compiler can see this violation statically and fails with an "illegal forward reference" to prevent it. However, an indirect read through a method call is harder to analyze statically, so the check is effectively deferred to runtime. At runtime, the variable is guaranteed to have a value (its default), so the operation is permitted.
"if you are trying to perform Direct Read immediately compile error we are going to get saying illegal forward reference"
Let's see this in action.
Example 1: Illegal Forward Reference (Direct Read)
This code tries to print x directly in a static block that appears before its declaration.
class Test {
static {
System.out.println(x); // Direct Read
}
static int x = 10;
}
The compiler sees that x is in a RIWO state at the point of the direct read. This is a clear violation of the rule, and the code fails to compile with an "illegal forward reference" error.
Example 2: Successful Indirect Read
This code calls a method, m1(), from the static block. The m1() method then reads x.
class Test {
static {
m1(); // Indirect Read via method call
}
public static void m1() {
System.out.println(x);
}
static int x = 10;
}
This code compiles and runs successfully. At runtime, m1() is called while x is in its RIWO state. Because this is an indirect read, it is permitted, and the program will print the default value of x, which is 0. After the static initialization is complete, the JVM then searches for the main method. Since it's not present, the program will terminate with a java.lang.NoSuchMethodError: main.
--------------------------------------------------------------------------------
Conclusion: Flow Over Fundamentals
The common "main is the entry point" rule is a simplification. The reality is a meticulous three-step sequence of identification, execution, and finally, the main method invocation. This sequence gives rise to the "Read Indirectly, Write Only" state, a temporary phase where variables exist with default values, leading to the counter-intuitive "illegal forward reference" error. Mastering this flow is the key to writing truly predictable, robust Java code, especially in applications with complex static initializers.
Now that you know main() isn't always the beginning, what other fundamental programming 'rules' are worth a second look?
No comments:
Post a Comment