JVM Internals

How the Java Virtual Machine Works Under the Hood

← Back to Index

What is the JVM?

Imagine you write a letter in English, but your friend only reads Spanish. You need a translator! The JVM (Java Virtual Machine) is like that translator - it takes your Java code (bytecode) and translates it into instructions the computer's processor can understand.

Real-World Analogy: Universal Remote Control

A universal remote can control any TV brand - Samsung, LG, Sony. You press the same button (bytecode), and the remote translates it to commands each TV understands. The JVM is like that universal remote for computers!

Why JVM Matters - Write Once, Run Anywhere (WORA)

Without JVM (Old way):
MyProgram.java → Compile for Windows → Windows.exe
MyProgram.java → Compile for Mac → Mac.app
MyProgram.java → Compile for Linux → Linux.bin
Problem: Need 3 different versions!

With JVM (Java way):
MyProgram.java → Compile ONCE → MyProgram.class (bytecode)
                          ↓
        ┌─────────────────┼─────────────────┐
        ↓                 ↓                 ↓
    JVM (Windows)    JVM (Mac)        JVM (Linux)
        ↓                 ↓                 ↓
    Runs on Windows  Runs on Mac      Runs on Linux

Solution: Same .class file runs everywhere!

The Promise: "Write once, run anywhere" - compile your Java code once, and it runs on any device with a JVM!

JVM Architecture - The Big Picture

┌──────────────────────────────────────────────┐
│         YOUR JAVA APPLICATION                │
│         (MyProgram.java source code)         │
└──────────────────────────────────────────────┘
                     ↓ javac (Java Compiler)
┌──────────────────────────────────────────────┐
│         BYTECODE (MyProgram.class)           │
│    Platform-independent instructions         │
└──────────────────────────────────────────────┘┌──────────────────────────────────────────────┐
│         JVM (Java Virtual Machine)           │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  CLASS LOADER SUBSYSTEM                │  │
│  │  • Loads .class files                  │  │
│  │  • Verifies bytecode                   │  │
│  │  • Prepares classes                    │  │
│  └────────────────────────────────────────┘  │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  RUNTIME DATA AREAS (Memory)           │  │
│  │  • Heap: Objects live here             │  │
│  │  • Stack: Method calls                 │  │
│  │  • Method Area: Class data             │  │
│  │  • PC Register: Instruction pointer    │  │
│  └────────────────────────────────────────┘  │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  EXECUTION ENGINE                      │  │
│  │  • Interpreter: Runs bytecode          │  │
│  │  • JIT Compiler: Optimizes hot code    │  │
│  │  • Garbage Collector: Cleans memory    │  │
│  └────────────────────────────────────────┘  │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  NATIVE METHOD INTERFACE (JNI)         │  │
│  │  Calls to C/C++ libraries              │  │
│  └────────────────────────────────────────┘  │
└──────────────────────────────────────────────┘┌──────────────────────────────────────────────┐
│     OPERATING SYSTEM (Windows/Mac/Linux)     │
└──────────────────────────────────────────────┘┌──────────────────────────────────────────────┐
│         HARDWARE (CPU, RAM, Disk)            │
└──────────────────────────────────────────────┘

Think of it like a restaurant:

Component 1: Class Loader Subsystem

The Class Loader is like a librarian who finds and organizes books (classes) when you need them.

Three Phases of Class Loading

Phase 1: Loading - Finding the Class

// When you write:
MyClass obj = new MyClass();

// JVM thinks:
"I need MyClass! Let me find it..."

Steps:
1. Search for MyClass.class file
   • Check classpath
   • Look in JAR files
   • Search directories
2. Read the .class file into memory
3. Create a Class object representing MyClass
4. Store it in Method Area

Phase 2: Linking - Verification, Preparation, Resolution

Verification (Security Check):
  ✓ Is this valid bytecode?
  ✓ Does it follow Java language rules?
  ✓ Are there security violations?
  ✓ Will it crash the JVM?
  → If any check fails: ClassFormatError or VerifyError

Preparation (Memory Allocation):
  • Allocate memory for static variables
  • Set default values:
    - Numbers (int, long, etc.) → 0
    - boolean → false
    - References (Object, String, etc.) → null

Resolution (Link References):
  • Convert symbolic names to actual memory addresses
  • Example: "MyClass.method()" → actual method location

Phase 3: Initialization - Running Static Code

public class Example {
    static int count = 10;  // Step 2: Initialize to 10

    static {
        System.out.println("Static block!");  // Step 3: Execute this
        count = 20;  // Step 4: Set to 20
    }

    static int doubled = count * 2;  // Step 5: doubled = 40
}

// First time Example is used:
// Step 1: count = 0 (default from Preparation)
// Step 2: count = 10 (initialization)
// Step 3: Static block executes → prints "Static block!"
// Step 4: count = 20
// Step 5: doubled = 40 (20 * 2)

System.out.println(Example.count);    // 20
System.out.println(Example.doubled);  // 40

Class Loader Hierarchy - Parent Delegation Model

┌──────────────────────────────┐
│  Bootstrap Class Loader      │  ← Written in C++
│  Loads: rt.jar, core classes │     java.lang.*
│  (String, Object, System)    │     java.util.*
└──────────────────────────────┘
            ↓ parent
┌──────────────────────────────┐
│  Extension Class Loader      │  ← Loads extensions
│  Loads: jre/lib/ext/*.jar    │     Optional packages
└──────────────────────────────┘
            ↓ parent
┌──────────────────────────────┐
│  Application Class Loader    │  ← Your application
│  Loads: CLASSPATH classes    │     MyClass.class
│  Your .class files, libs     │     third-party JARs
└──────────────────────────────┘

Parent Delegation:
1. Application ClassLoader asked to load MyClass
2. Delegates to Extension ClassLoader (parent)
3. Extension delegates to Bootstrap (grandparent)
4. Bootstrap tries, doesn't find it
5. Extension tries, doesn't find it
6. Application tries, finds and loads it!

Why? Security! Core classes (String, Object) always
loaded by Bootstrap - you can't replace them!

Component 2: Runtime Data Areas (Memory)

1. Heap - Where Objects Live

The Heap is like a warehouse where all your objects are stored. It's shared by all threads.

public class MemoryDemo {
    public static void main(String[] args) {
        Person alice = new Person("Alice", 25);  // Created in HEAP
        Person bob = new Person("Bob", 30);    // Created in HEAP

        String message = "Hello";  // String in HEAP (String pool)
        int[] numbers = {1, 2, 3};  // Array in HEAP
    }
}

HEAP Memory Visualization:
┌───────────────────────────────┐
│ HEAP (Shared by all threads) │
├───────────────────────────────┤
│ Person object                 │
│ name: "Alice"                 │  ← alice variable points here
│ age: 25                       │
├───────────────────────────────┤
│ Person object                 │
│ name: "Bob"                   │  ← bob variable points here
│ age: 30                       │
├───────────────────────────────┤
│ String: "Hello"               │  ← message variable points here
├───────────────────────────────┤
│ int[] {1, 2, 3}               │  ← numbers variable points here
└───────────────────────────────┘

2. Stack - Method Call Stack (Thread-Specific)

Each thread gets its own Stack - like a stack of plates. Methods are added on top (push) and removed (pop) in LIFO order.

public class StackDemo {
    public static void main(String[] args) {  // Frame 1
        int x = 10;
        int result = calculate(x);
        System.out.println(result);
    }

    public static int calculate(int a) {  // Frame 2
        int b = 20;
        return multiply(a, b);
    }

    public static int multiply(int p, int q) {  // Frame 3
        int result = p * q;
        return result;
    }
}

STACK Evolution (Thread's Stack):

Step 1: main() starts
┌─────────────────────┐
│ main()              │  ← Current execution
│ x = 10              │
│ result = ?          │
└─────────────────────┘

Step 2: calculate() called
┌─────────────────────┐
│ calculate()         │  ← Current execution
│ a = 10              │
│ b = 20              │
├─────────────────────┤
│ main()              │
│ x = 10              │
│ result = ?          │
└─────────────────────┘

Step 3: multiply() called
┌─────────────────────┐
│ multiply()          │  ← Current execution
│ p = 10, q = 20      │
│ result = 200        │
├─────────────────────┤
│ calculate()         │
│ a = 10, b = 20      │
├─────────────────────┤
│ main()              │
│ x = 10              │
│ result = ?          │
└─────────────────────┘

Step 4: multiply() returns 200, frame popped
┌─────────────────────┐
│ calculate()         │  ← Current execution
│ a = 10, b = 20      │
│ return value: 200   │
├─────────────────────┤
│ main()              │
│ x = 10              │
│ result = ?          │
└─────────────────────┘

Step 5: calculate() returns 200, frame popped
┌─────────────────────┐
│ main()              │  ← Current execution
│ x = 10              │
│ result = 200        │  ✓ Got the result!
└─────────────────────┘

Step 6: main() finishes, stack empty, program ends

What Goes in Stack vs Heap?

Stack Heap
Local variables (primitives) Objects (new keyword)
Method parameters Instance variables
Method call information Arrays
References to objects Strings
Fast access (LIFO) Slower access (GC managed)
Thread-specific Shared by all threads
Automatic cleanup (pop) Garbage Collector cleanup

3. Method Area (Metaspace in Java 8+)

Stores class-level information shared by all threads.

Method Area contains:
• Class structure (name, parent, interfaces)
• Method bytecode
• Static variables
• Constant pool (string literals, constants)
• Field information (names, types)

public class Example {
    static int count = 0;  // Stored in Method Area
    static final String APP_NAME = "MyApp";  // Method Area

    public void doWork() {  // Bytecode stored in Method Area
        // method body...
    }
}

4. Program Counter (PC) Register

Keeps track of which instruction to execute next - like a bookmark in a book.

5. Native Method Stack

For methods written in other languages (C/C++) accessed via JNI (Java Native Interface).

Component 3: Execution Engine

1. Interpreter - Line-by-Line Execution

Reads bytecode one instruction at a time and executes it. Simple but slow.

Your Java code:
int x = 5;
int y = 10;
int sum = x + y;

Compiled to bytecode:
0: bipush 5        // Push 5 onto operand stack
2: istore_1        // Store in local variable 1 (x)
3: bipush 10       // Push 10 onto stack
5: istore_2        // Store in local variable 2 (y)
6: iload_1         // Load x onto stack
7: iload_2         // Load y onto stack
8: iadd            // Add top two stack values
9: istore_3        // Store result in variable 3 (sum)

Interpreter executes each instruction one by one

2. JIT (Just-In-Time) Compiler - Performance Booster

The game-changer! JIT identifies "hot" code (frequently executed) and compiles it to native machine code for lightning-fast execution.

How JIT Works:

Phase 1: Profiling
  JVM watches which code runs frequently
  Example: "This loop ran 10,000 times!"

Phase 2: Compilation
  JIT compiles hot code to native machine code
  Bytecode → Native CPU instructions

Phase 3: Optimization
  • Inline methods (remove function call overhead)
  • Dead code elimination
  • Loop unrolling
  • Constant folding

Example:
for (int i = 0; i < 1000000; i++) {
    result += calculate(i);  // Called 1 million times!
}

Without JIT:
  Interpreter executes calculate() 1 million times = SLOW

With JIT:
  First ~100 calls: Interpreted (JIT profiling)
  JIT detects: "This is hot code!"
  JIT compiles to native code (once)
  Remaining 999,900 calls: Native code = FAST!

Result: 10-100x faster!

3. Garbage Collector - Memory Janitor

Automatically finds and removes unused objects. Covered in detail in next topic!

Complete Example: What Happens When You Run Java Code

public class HelloWorld {
    public static void main(String[] args) {
        String message = "Hello, World!";
        System.out.println(message);
    }
}

Complete Step-by-Step Journey:

  1. Compilation Phase (Developer Machine):
    • Run: javac HelloWorld.java
    • Compiler reads source code
    • Checks syntax, types
    • Generates: HelloWorld.class (bytecode)
  2. JVM Startup:
    • Run: java HelloWorld
    • Operating System loads JVM
    • JVM initializes memory areas (Heap, Stack, Method Area)
    • JVM starts main thread
  3. Class Loading:
    • Application ClassLoader searches for HelloWorld.class
    • Reads bytecode into memory
    • Verification: Checks bytecode is safe
    • Preparation: Allocates memory for static variables
    • Resolution: Links references
    • Stores class metadata in Method Area
  4. Execution Begins:
    • JVM finds main() method
    • Creates stack frame for main() on thread's stack
    • Execution starts at first instruction
  5. Line 1: String message = "Hello, World!";
    • String literal "Hello, World!" created in Heap (String pool)
    • Variable 'message' created in Stack (local variable)
    • 'message' holds reference to String in Heap
  6. Line 2: System.out.println(message);
    • JVM loads System class (if not already loaded)
    • Accesses static field 'out' (PrintStream object)
    • Calls println() method
    • Native method writes to console
    • Output appears: "Hello, World!"
  7. Method Return:
    • main() completes
    • Stack frame removed (popped)
    • Local variables disappear
  8. Garbage Collection:
    • String object in Heap no longer referenced
    • Marked for collection (happens later)
    • Memory will be reclaimed
  9. JVM Shutdown:
    • No more non-daemon threads running
    • JVM performs cleanup
    • Returns control to Operating System
    • Program terminates

Why Understanding JVM Matters

For Developers:

Common JVM Flags:

# Set max heap size to 2GB
java -Xmx2g MyProgram

# Set initial heap size to 512MB
java -Xms512m MyProgram

# Set stack size to 1MB
java -Xss1m MyProgram

# Enable GC logging
java -Xlog:gc MyProgram

# Print JVM settings
java -XX:+PrintFlagsFinal MyProgram
Remember

The JVM is your friend - it handles memory, optimization, garbage collection, and platform compatibility so you can focus on writing great code!

Think of it as: You drive the car (write code), JVM is the engine that makes it all work!