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!
- Your code: The button you press (bytecode)
- JVM: The universal remote (translator)
- Different computers: Different TV brands
- Result: Same action on all devices!
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:
- Class Loader: Waiter who brings ingredients (classes) from storage
- Memory Areas: Kitchen workspace (heap, stack, etc.)
- Execution Engine: Chef who cooks (executes) your code
- Garbage Collector: Dishwasher who cleans up unused items
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:
- Compilation Phase (Developer Machine):
- Run:
javac HelloWorld.java - Compiler reads source code
- Checks syntax, types
- Generates:
HelloWorld.class(bytecode)
- Run:
- JVM Startup:
- Run:
java HelloWorld - Operating System loads JVM
- JVM initializes memory areas (Heap, Stack, Method Area)
- JVM starts main thread
- Run:
- 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
- Execution Begins:
- JVM finds main() method
- Creates stack frame for main() on thread's stack
- Execution starts at first instruction
- 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
- 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!"
- Method Return:
- main() completes
- Stack frame removed (popped)
- Local variables disappear
- Garbage Collection:
- String object in Heap no longer referenced
- Marked for collection (happens later)
- Memory will be reclaimed
- JVM Shutdown:
- No more non-daemon threads running
- JVM performs cleanup
- Returns control to Operating System
- Program terminates
Why Understanding JVM Matters
For Developers:
- Performance Tuning: Adjust heap size, GC settings for optimal performance
- Debugging: Understand stack traces, OutOfMemoryErrors
- Memory Management: Write memory-efficient code
- Thread Safety: Understand how threads share heap but have separate stacks
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
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!