The JVM (Java Virtual Machine) is a software-based Runtime that:

FeatureWhat It Does
Reads bytecodeExecutes the .class files
Interprets or compilesUses an interpreter and JIT (Just-In-Time) compiler to convert bytecode to machine code
Manages memoryAllocates heap/stack, runs Garbage Collection
Handles threadsMaps Java threads to OS threads
Loads classesDynamically loads .class files as needed
Ensures safetyVerifies 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.out and "Sum: " string literal go here
  • 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.out is a static field pointing to a PrintStream object.

  • println is a method call → JVM:

    • Resolves it via virtual method table

    • Pushes a new stack frame

    • Handles string concatenation ("Sum: " + sum) via StringBuilder

  • println uses native methods (via JNI) to call platform-specific I/O (e.g., Linux write() 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:

ComponentUsed For
HeapObjects like Strings, System.out
StackLocal variables (a, b, sum), operand stack, return address
Method AreaClass definitions, constant pool, metadata
PC RegisterTracks bytecode instruction being executed
Native StackFor 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;