Skip to content

Latest commit

 

History

History
825 lines (578 loc) · 13.6 KB

File metadata and controls

825 lines (578 loc) · 13.6 KB

Flow of a Java Program — Deep Dive

To truly understand Java, don't stop at:

.java → .class → JVM

The complete flow involves source code, compilation, bytecode, class loading, linking, initialization, execution, memory, JIT compilation, and garbage collection.


1. The Complete Flow

        Java Source Code
             .java
               │
               ▼
        Java Compiler
            javac
               │
               ▼
        Bytecode
          .class
               │
               ▼
        Class Loader
               │
               ▼
       Loading → Linking
               │
       ┌───────┴────────┐
       ▼                ▼
 Verification       Preparation
                         │
                      Resolution
                         │
                         ▼
                 Class Initialization
                         │
                         ▼
                    JVM Runtime
                         │
              ┌──────────┴──────────┐
              ▼                     ▼
          Interpreter              JIT
              │                     │
              └──────────┬──────────┘
                         ▼
                  Native Execution
                         │
                         ▼
                       CPU
                         │
                         ▼
                      Output

Let's understand each stage.


2. Stage 1 — Write the Source Code

You write a Java program in a .java file.

Example:

public class Hello {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;

        int sum = a + b;

        System.out.println(sum);
    }
}

The source file is:

Hello.java

At this point, it is simply text written according to Java's language rules.


3. Stage 2 — Compilation

We compile it using:

javac Hello.java

The Java compiler performs several checks.

Syntax checking

For example:

int x = ;

is invalid Java syntax.

Type checking

For example:

int age = "Hello";

is invalid because a String cannot be assigned to an int.

Symbol and declaration checks

The compiler also checks whether variables, methods, classes, etc. are used consistently.

If compilation succeeds:

Hello.java
    ↓
   javac
    ↓
Hello.class

4. Stage 3 — Bytecode

The .class file contains JVM bytecode.

This is extremely important.

The compiler does not normally produce one specific operating system's machine code.

Instead:

Java Source
    ↓
Java Compiler
    ↓
JVM Bytecode

That bytecode can be executed by an appropriate JVM implementation.

This is the foundation of Java's portability.


5. Stage 4 — Starting the Program

We normally launch a standalone Java program with:

java Hello

The Java launcher starts an appropriate JVM/runtime and requests execution of the Hello class.

The JVM needs to find the class.

That leads to class loading.


6. Stage 5 — Class Loading

The JVM uses a Class Loader to load class definitions.

Conceptually:

Hello.class
     ↓
Class Loader
     ↓
JVM memory

The class loader doesn't necessarily load every class in the application immediately.

Classes can be loaded when they are needed.

This is important because Java applications may depend on hundreds or thousands of classes.


7. Stage 6 — Linking

After loading, the JVM performs the linking process.

Linking is commonly discussed in three major parts:

Loading
   ↓
Verification
   ↓
Preparation
   ↓
Resolution

Let's break those down.


7.1 Verification

The JVM checks that the bytecode is structurally valid and follows JVM rules.

Conceptually:

Bytecode
   ↓
Verifier
   ↓
Is it valid?

This helps prevent malformed bytecode from being executed improperly.


7.2 Preparation

The JVM prepares memory for class-level data and establishes required runtime structures.

For example, static fields receive appropriate initial default values during this stage.


7.3 Resolution

Symbolic references in the class may be resolved to concrete runtime references.

For example, bytecode might refer to another class or method symbolically.

The JVM can resolve those references as required.

Resolution can be performed lazily depending on the JVM implementation and circumstances.


8. Stage 7 — Class Initialization

After loading/linking, a class may need to be initialized.

Consider:

class Test {
    static int x = 100;
}

The JVM initializes the class when its initialization conditions are met.

Static initialization may involve:

static int x = 100;

static {
    System.out.println("Class initialized");
}

The important distinction is:

Loading
   ≠
Initialization

A class can be loaded before its initialization occurs.


9. Stage 8 — Finding main()

For a traditional standalone Java application, the runtime looks for:

public static void main(String[] args)

For example:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

The main() method becomes the starting point for application execution.


10. Stage 9 — JVM Runtime Memory

Once execution begins, the JVM manages several important runtime memory areas.

A simplified model is:

JVM Runtime
│
├── Heap
├── Java Stacks
├── Method Area
├── PC Registers
└── Native Method Stacks

Let's understand them.


11. Heap

The heap is where Java objects are generally allocated.

Example:

Student s = new Student();

The new Student() creates an object whose storage is associated with the heap.

Conceptually:

Stack
  │
  │ s
  ▼
Heap
  │
  └── Student object

The heap is also the main area managed by the garbage collector.


12. Java Stack

Each thread has its own Java stack.

When a method is called, a stack frame is created.

For example:

main()
  ↓
calculate()
  ↓
add()

Conceptually:

Stack
┌─────────────┐
│ add() frame │
├─────────────┤
│calculate()  │
├─────────────┤
│main() frame │
└─────────────┘

When add() finishes, its frame is removed.

This is why method calls follow a Last In, First Out (LIFO) structure.


13. Stack vs Heap

This is one of the most important beginner concepts.

Consider:

Student s = new Student();

A simplified mental model is:

Stack                  Heap
──────                  ────
s ───────────────────→ Student object

The variable s is a reference.

The object created with new is associated with heap memory.

This is a simplified model; the JVM/JIT may optimize actual storage and object behavior in ways that differ from this conceptual picture.


14. Stage 10 — Bytecode Execution

Now the JVM needs to execute the bytecode.

There are two major concepts to understand:

Interpreter

The JVM can interpret bytecode instructions.

Bytecode
   ↓
Interpreter
   ↓
Execution

JIT compiler

Frequently executed code can be compiled into optimized native code.

Bytecode
   ↓
JVM observes execution
   ↓
Hot code
   ↓
JIT
   ↓
Native machine code

Modern JVMs use sophisticated combinations of interpretation and JIT compilation.


15. What is "hot code"?

Suppose your program has:

for (int i = 0; i < 1_000_000; i++) {
    calculate(i);
}

The method calculate() may execute an enormous number of times.

The JVM can recognize that this code is frequently executed.

That makes it a candidate for optimization.

Rarely executed code
      ↓
Interpretation may be enough

Frequently executed code
      ↓
JIT optimization
      ↓
Native machine code

This is called adaptive optimization.


16. Why Java can be fast

People sometimes think:

"Java is slow because it runs inside a virtual machine."

That is an outdated oversimplification.

Modern JVMs perform sophisticated runtime optimization.

They can use:

  • JIT compilation
  • method inlining
  • escape analysis
  • speculative optimization
  • optimized garbage collectors
  • profile-guided runtime decisions

So the JVM isn't simply "interpreting everything."


17. Stage 11 — Garbage Collection

Suppose:

Student s = new Student();

Later:

s = null;

If there are no other reachable references to that object, it may become eligible for garbage collection.

Conceptually:

Student object
     ↓
No longer reachable
     ↓
Eligible for GC
     ↓
Garbage Collector
     ↓
Memory reclaimed

Important:

Becoming unreachable does not mean the object is immediately destroyed.

The garbage collector decides when and how memory is reclaimed.


18. Does Java automatically free everything?

Java automatically manages ordinary object memory through garbage collection.

But developers still need to manage resources such as:

  • files
  • sockets
  • database connections
  • external resources

For example:

try (FileInputStream file =
         new FileInputStream("data.txt")) {

    // use file

}

The try-with-resources mechanism helps ensure the resource is closed.

So:

Garbage collection manages memory; it is not a replacement for resource management.


19. Stage 12 — Output

Suppose we execute:

System.out.println(10 + 20);

The result is:

30

The overall journey was:

Source
  ↓
Compiler
  ↓
Bytecode
  ↓
Class Loader
  ↓
Verification
  ↓
Initialization
  ↓
JVM execution
  ↓
Interpreter/JIT
  ↓
CPU
  ↓
Output

20. Complete Example

Let's follow one tiny program.

public class Demo {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;

        int result = a + b;

        System.out.println(result);
    }
}

Step 1

File:

Demo.java

Step 2

Compile:

javac Demo.java

Step 3

Generated:

Demo.class

Step 4

Run:

java Demo

Step 5

JVM loads Demo.

Step 6

JVM verifies and links the class.

Step 7

Class initialization occurs as required.

Step 8

main() executes.

Step 9

The JVM executes the bytecode.

Step 10

The result is printed:

30

21. The Flow at Different Levels

Beginner view

.java
 ↓
javac
 ↓
.class
 ↓
JVM
 ↓
Output

Intermediate view

Source
 ↓
Compilation
 ↓
Bytecode
 ↓
Class Loading
 ↓
Verification
 ↓
Execution
 ↓
Output

Advanced view

Source
 ↓
javac
 ↓
Bytecode
 ↓
Class Loader
 ↓
Loading
 ↓
Linking
 ├── Verification
 ├── Preparation
 └── Resolution
 ↓
Initialization
 ↓
Runtime Data Areas
 ↓
Interpreter + JIT
 ↓
Native Machine Code
 ↓
CPU
 ↓
Output

🔥 The Most Important Distinction

Do not memorize this as:

Java → JVM → CPU

Understand the layers:

┌─────────────────────────────┐
│       Java Source           │
│          .java              │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│       javac Compiler        │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│        JVM Bytecode         │
│           .class            │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│        Class Loader         │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│   Verification / Linking    │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│       JVM Runtime           │
│                             │
│  Heap | Stack | GC | JIT    │
└─────────────┬───────────────┘
              ↓
┌─────────────────────────────┐
│    Native Machine Code      │
└─────────────┬───────────────┘
              ↓
             CPU

🎯 Final memory trick

Remember the 8-step flow:

Write → Compile → Bytecode → Load → Link → Initialize → Execute → Output

Or the short version:

.java → javac → .class → Class Loader → JVM → JIT → CPU → Output

Once you understand this flow, concepts like JDK, JVM, bytecode, class loading, heap, stack, garbage collection, and JIT stop being separate topics—they become different parts of the same Java execution process.