The JVM (Java Virtual Machine) is a software-based Runtime that:
| Feature | What It Does |
|---|---|
| Reads bytecode | Executes the .class files |
| Interprets or compiles | Uses an interpreter and JIT (Just-In-Time) compiler to convert bytecode to machine code |
| Manages memory | Allocates heap/stack, runs Garbage Collection |
| Handles threads | Maps Java threads to OS threads |
| Loads classes | Dynamically loads .class files as needed |
| Ensures safety | Verifies bytecode for type safety and structure |
✅ The JVM makes Java portable. You compile once, run anywhere with a JVM — Linux, Windows, macOS, cloud, etc.
Perfect — now we’re diving deep: how the JVM actually executes a basic Java program, like summing two numbers — all the way down to threads, stack frames, memory, JIT, and CPU.
Let’s walk through a step-by-step real JVM execution path with a simple example:
🧪 Java Code Example
public class Sum {
public static void main(String[] args) {
int a = 5;
int b = 7;
int sum = a + b;
System.out.println("Sum: " + sum);
}
}🔄 Step-by-Step JVM Execution Flow
1. You compile the code
-
Command:
javac Sum.java -
Output:
Sum.class(contains JVM bytecode)
2. JVM starts and creates a thread
-
Command:
java Sum -
The Java launcher loads the JVM into memory.
-
JVM creates the main thread.
-
OS allocates a native thread for it (managed by OS thread scheduler).
3. Class Loader loads Sum.class
-
The JVM’s class loader subsystem:
-
Finds and loads
Sum.class -
Loads dependencies (like
java.lang.String,System, etc.) -
Verifies bytecode structure
-
Prepares class metadata in the method area
-
4. Execution Engine begins
-
It invokes
main(String[] args)method. -
A stack frame is created for the
main()method. -
JVM starts executing bytecode using the interpreter.
5. Memory Allocation: Runtime Data Areas
-
Stack:
-
Holds the current method’s local variables:
a,b,sum -
Each method call = new stack frame
-
-
Heap:
- Objects like
System.outand"Sum: "string literal go here
- Objects like
-
PC Register:
- Keeps track of the current bytecode instruction for each thread
-
Method Area:
- Stores class-level info like constant pool, field/method metadata
6. Bytecode Execution (for a + b)
Bytecode for int sum = a + b; might look like:
iload_1 ; load 'a' (5) onto operand stack
iload_2 ; load 'b' (7)
iadd ; add the two ints
istore_3 ; store result in 'sum'-
JVM uses the operand stack inside the stack frame.
-
No registers in JVM bytecode — it’s a stack-based VM.
7. JIT Compiler Kicks In (if needed)
-
After a few runs or loops (not in this example), JIT will:
-
Detect hot methods (e.g.,
main()if repeated) -
Compile bytecode to native machine code
-
Cache it in memory for fast re-use
-
⚡ This makes performance comparable to C/C++ after warm-up.
8. System.out.println(...) Execution
-
System.outis a static field pointing to aPrintStreamobject. -
printlnis a method call → JVM:-
Resolves it via virtual method table
-
Pushes a new stack frame
-
Handles string concatenation (
"Sum: " + sum) viaStringBuilder
-
-
printlnuses native methods (via JNI) to call platform-specific I/O (e.g., Linuxwrite()syscall)
9. CPU and Thread Level
-
The OS thread allocated for the JVM’s main thread is scheduled by the OS CPU scheduler.
-
When JIT compiles bytecode:
-
Native machine instructions are executed directly on the CPU
-
JVM maps logical threads to OS/native threads using
Thread,ExecutorService, etc.
-
-
Thread-local stacks, registers, and JVM-internal memory structures are used per thread.
10. Program Finishes
-
main()method completes → its stack frame is popped -
JVM main thread exits
-
JVM shuts down (unless other non-daemon threads are running)
🔹 Memory Flow Summary:
| Component | Used For |
|---|---|
| Heap | Objects like Strings, System.out |
| Stack | Local variables (a, b, sum), operand stack, return address |
| Method Area | Class definitions, constant pool, metadata |
| PC Register | Tracks bytecode instruction being executed |
| Native Stack | For calling into C/C++ via JNI (println internals) |
✅ TL;DR
When summing two numbers, the JVM:
-
Loads the class
-
Starts a thread
-
Creates a stack frame
-
Executes bytecode (via interpreter or JIT)
-
Uses stack for variables
-
Uses heap for objects
-
Calls native methods via JNI
-
Runs on top of an OS thread scheduled on your CPU
flowchart TD A["Java Source Code: Sum.java"] --> B["Java Compiler: javac"] B --> C["Bytecode File: Sum.class"] C --> D["JVM Starts Up"] D --> E["Main Thread Created"] E --> F["Class Loader"] F --> |"Loads Sum.class\nLoads referenced classes\n(e.g., System, String)"| G["Bytecode Verifier"] G --> |"Ensures code is valid,\ntype-safe, secure"| H["Execution Engine Starts"] H --> I["Create Stack Frame for main()"] I --> J["Allocate Local Variables"] J --> |"int a = 5\nint b = 7"| K["Bytecode Executed"] K --> |"iload_1 → push a (5)\niload_2 → push b (7)\niadd → pop 5 & 7, push 12\nistore_3 → store 12 in sum"| L["Heap Allocation"] L --> |"Allocate String 'Sum: '\nAllocate System.out reference"| M["Call println()"] M --> |"Load PrintStream object\nCall println via virtual method table"| N["Invoke Native Code via JNI"] N --> |"println internally calls OS-level I/O\n(e.g., write to stdout)"| O["Output to Terminal: 'Sum: 12'"] O --> P["main() Completes"] P --> Q["Stack Frame for main() Popped"] Q --> R["JVM Shuts Down (if no other threads)"] classDef compilation fill:#f9d5e5,stroke:#333,stroke-width:1px; classDef loading fill:#eeeeee,stroke:#333,stroke-width:1px; classDef execution fill:#d5f9e6,stroke:#333,stroke-width:1px; classDef memory fill:#e6e6fa,stroke:#333,stroke-width:1px; classDef output fill:#fff4cc,stroke:#333,stroke-width:1px; classDef cleanup fill:#f0f0f0,stroke:#333,stroke-width:1px; class A,B,C compilation; class D,E,F,G loading; class H,I,J,K execution; class L,M,N memory; class O output; class P,Q,R cleanup;