JVM vs JRE vs JDK


Java programs don't talk directly to your CPU. They run on the Java Virtual Machine (JVM) — think of it as a small OS for Java.

On top of the JVM you have the JRE (runtime you install to run apps) and the JDK (the JRE plus developer tools you use to build apps).


JVM (Java Virtual Machine) Internals


The JVM is the runtime engine that executes Java bytecode. It provides:
  • Platform independence (write once, run anywhere).
  • Memory management (automatic Garbage Collection).
  • Runtime optimizations (JIT compiler).
  • Security (verifies bytecode before running). 


1. JVM Architecture (Big Picture)

┌────────────────────────────┐ │ Class Loader Subsystem │ └─────────────┬──────────────┘ │ ┌─────────────┴──────────────┐ │ Runtime Data Areas │ │ - Method Area (Metaspace) │ │ - Heap (Young/Old) │ │ - Java Stacks │ │ - PC Register │ │ - Native Method Stack │ └─────────────┬──────────────┘ │ ┌─────────────┴──────────────┐ │ Execution Engine │ │ - Interpreter │ │ - JIT Compiler │ │ - Garbage Collector │ └─────────────┬──────────────┘ │ ┌─────────────┴──────────────┐ │ JNI & Native Libraries │ └────────────────────────────┘

Main Components:

  • Class Loader Subsystem – loads .class files.
  • Runtime Data Areas – memory model (Heap, Stacks, etc.).
  • Execution Engine – runs the code (Interpreter + JIT + GC).
  • JNI (Java Native Interface) – connects Java with C/C++ native code.
  • Native Libraries – platform-specific libraries. 


2. Class Loader Subsystem


Class Loader

  • Purpose: Responsible for loading .class files into JVM memory so they can be executed.

Steps involved:

  1. Loading:
    • Reads .class bytecode (from disk, JAR, or network).
    • Creates a Class object in the heap.
  2. Linking:
    • Verification: Checks bytecode is safe (no illegal memory access).
    • Preparation: Allocates memory for static variables.
    • Resolution: Replaces symbolic names in bytecode with actual memory references.
  3. Initialization:
    • Executes static initializers (static variable assignments and static blocks) via the special method <clinit>.

                Example:


                public class MyClass {

                    static int x = 10;                                                            // static variable

                    static { System.out.println("Static block executed"); }  // static block

                }

    • During initialization, JVM executes:
      1. x = 10
      2. The static block: System.out.println("Static block executed")

Class Loader Hierarchy:

  • Bootstrap: Loads core Java classes (e.g., java.lang.String, java.util.ArrayList)
  • Extension / Platform: Loads standard extension libraries (e.g., javax.crypto.Cipher)
  • Application / System: Loads user-defined classes (e.g., com.myapp.Employee)
  • Custom: User-created loaders for special needs (e.g., encrypted classes via MyClassLoader)
👉 In short: Class Loaders read, verify, link, and initialize classes, making them ready for execution in the JVM.



3. JVM Runtime Data Areas (JVM Memory Model)


JVM memory is divided into two categories:

  • Runtime Data Areas (used by your Java program).
  • Native Memory (used internally by JVM).

(a) Method Area (Metaspace in Java 8+)
  • Stores:
    • Class metadata – information about class structure (fields, methods, constructors, etc.)
    • Runtime Constant Pool (RCP) – symbolic references and constants used by the class
  • Static variables: Only the references are in Metaspace; their values live in the heap (in Java 7 and earlier, both references and values were in PermGen)
  • Shared by all threads
  • Memory mapping:

    • Java 7 and earlier → PermGen
    • Java 8+ → Metaspace (native memory, grows dynamically)

    Note: The String Pool is in the heap since Java 7, not in Metaspace.


(b) Heap Memory
  • Largest memory area. Stores:
    • Objects & arrays – all instances of classes
    • Static variable values – actual data of static fields
    • String Pool – interned strings (special area inside the heap)
    • Object-related runtime data – e.g., object headers, GC info
  • Shared by all threads.
  • Garbage Collected.

Divisions:

  • Young Generation (new objects)
    • Eden: where new objects are created
    • Survivor (S0, S1): objects that survive a GC cycle
  • Old Generation (Tenured): long-lived objects
  • Humongous Objects Space (G1 GC): very large objects (e.g., new int[10_000_000])

Errors:

  • OutOfMemoryError: Java heap space → heap is full 


(c) Java Stack (per thread)
  • Each thread has its own stack, separate from other threads.
  • Purpose: Keeps track of method calls.

Each method call creates a stack frame containing:

  1. Local variables – method parameters and local variables
  2. Operand stack – used for intermediate calculations during execution
  3. Return address – where to continue after the method finishes
  • Stack frames are destroyed automatically when a method exits.

Errors:

  • StackOverflowError → happens with deep recursion or very large stack usage
  • OutOfMemoryError → occurs if JVM cannot create a new thread (because its stack cannot be allocated)

Example:


public class StackExample {

    public static void main(String[] args) {

        methodA();

    }


    public static void methodA() {

        int x = 10;      // stored in stack frame of methodA

        int y = 20;      // also in methodA's frame

        methodB();       // new stack frame created for methodB

    }


    public static void methodB() {

        int z = 30;      // stored in stack frame of methodB

    }

}

When methodB finishes, its stack frame is removed, and execution returns to methodA’s frame.


(d) Program Counter (PC) Register

  • Each thread has its own PC register.
  • Stores: the address of the current bytecode instruction being executed.

        Example:


        public class PCExample {

            public static void main(String[] args) {

                int a = 10;                                           // PC points here first

                int b = 20;                                           // then PC moves here

                int sum = a + b;                                  // then here

                System.out.println(sum);                    // and finally here

            }

        }

  • As the program executes, the PC register updates to point to the next bytecode instruction for the current thread.
  • Important: Each thread has a separate PC, so threads execute independently.


(e) Native Method Stack

  • Used by the JVM to execute native methods—methods written in languages like C or C++ (not Java).
  • Works similarly to the Java stack, meaning it keeps track of method calls, local variables, and return addresses.
  • Each thread has its own native method stack, just like the Java stack.

        Example:


        public class NativeExample {

            static { System.loadLibrary("mylib"); }         // load C/C++ library

            public native void nativeMethod();               // declare a native method


            public static void main(String[] args) {

                new NativeExample().nativeMethod();      // JVM uses native stack to execute this

            }

        }

  • When nativeMethod() runs, the JVM pushes a frame on the native method stack instead of the Java stack.
  • Useful for calling system-level code or performance-critical operations written in C/C++.

(f) Native Memory (outside JVM Heap)
  • Memory used by the JVM outside of the heap, in the OS-managed native memory.
  • Stores JVM internal structures and compiled code, not directly accessible by Java programs.

Main components:

  1. Code Cache:
    • Stores JIT-compiled native code (machine code generated from Java bytecode).
    • Helps the JVM execute code faster than interpreting bytecode.
  2. GC Structures:
    • Data the garbage collector uses to track objects, regions, and references.
  3. Thread Control Structures:
    • Information needed to manage threads, such as thread stacks, locks, and scheduling data.

Example:

  • When JVM compiles a hot method (frequently used) using JIT, the native instructions are stored in the Code Cache, not the Java heap.
  • Thread data like PC register or native stacks are managed here.
👉 In short: Native Memory is JVM’s workspace for compiled code and internal bookkeeping, outside of Java objects and heap memory. 



4. Execution Engine

  • Often called the “CPU of the JVM” because it actually runs your Java code.

Main components:

  1. Interpreter:
    • Reads and executes bytecode line by line.
    • Simple but slower for repeated code execution.
  2. JIT (Just-In-Time) Compiler:
    • Converts frequently used (hot) bytecode into native machine code.
    • Makes execution much faster than interpreting line by line.
  3. Garbage Collector (GC):
    • Automatically frees memory occupied by objects that are no longer in use.
    • Helps prevent memory leaks and manage heap efficiently.
👉 In short: The Execution Engine interprets, compiles, and manages memory so Java programs can run efficiently on the CPU.



5. JNI (Java Native Interface)

  • Purpose: Allows Java programs to call native code written in languages like C or C++.
  • Use cases:
    • Accessing operating system functions not available in Java
    • Interacting with device drivers
    • Using high-performance or system-level libraries

    Example:


    public class JNIExample {

        static { System.loadLibrary("mylib"); }     // load C/C++ library

        public native void nativeMethod();          // declare a native method


        public static void main(String[] args) {

            new JNIExample().nativeMethod();    // JVM calls C/C++ code here

        }

    }


👉 In short: JNI is the bridge between Java and native code, letting Java interact with system-level resources or optimized libraries. 


6. JVM Workflow (Journey of a Program)

  1. Compilation:
    • javac MyClass.java → produces MyClass.class (bytecode).
  2. Class Loading:
    • Class Loader loads the .class file into JVM memory.
  3. Bytecode Verification:
    • JVM checks bytecode for security and correctness before execution.
  4. Execution:
    • Interpreter executes bytecode line by line.
    • JIT Compiler optimizes frequently used code paths into native machine code for faster execution.
  5. Memory Management:
    • Objects are created in the heap.
    • Garbage Collector (GC) automatically clears objects that are no longer used.
👉 In short: The JVM compiles, loads, verifies, executes, and manages memory for your Java program. 



Example: Employee Object in JVM Memory (Java 8+)

class Employee {

    String name;
    int age;
}

public class Test {
    public static void main(String[] args) {
        Employee emp = new Employee();  
        emp.name = "Siraj";             
        emp.age = 30;
        System.out.println(emp.name);
    }
}

👉 Let’s build this step by step for Java 8+ (where String Pool is in Heap).

Step 1: Before object creation 

When the program starts, classes are loaded into Metaspace, and string literals are created in the String Pool (Heap).
Stack (main) Heap Method Area (Metaspace) ┌───────────────────┐ ┌─────────────────────────┐ ┌───────────────────────────────┐ │ │ │ String Pool │ │ Class metadata: │ │ │ │ "Siraj" │ │ Employee │ │ │ └─────────────────────────┘ │ Test │ └───────────────────┘ └───────────────────────────────┘


Step 2: After Employee emp = new Employee();

A new Employee object is created in the Heap. The stack now holds a reference.
Stack (main) Heap Method Area (Metaspace) ┌───────────────────┐ ┌─────────────────────────┐ ┌───────────────────────────────┐ │ emp ───────────► │────►│ Employee Object │ │ Class metadata: │ └───────────────────┘ │ name = null │ │ Employee │ │ age = 0 │ │ Test │ └─────────────────────────┘ └───────────────────────────────┘ │ ▼ ┌─────────────────────────┐ │ String Pool (Heap) │ │ "Siraj" │ └─────────────────────────┘


Step 3: After emp.name = "Siraj"; emp.age = 30;

The name field now points to the "Siraj" object in the String Pool.
Stack (main) Heap Method Area (Metaspace) ┌───────────────────┐ ┌─────────────────────────┐ ┌───────────────────────────────┐ │ emp ───────────► │────►│ Employee Object │ │ Class metadata: │ └───────────────────┘ │ name ───────────────► │ │ Employee │ │ age = 30 │ │ Test │ └─────────────────────────┘ └───────────────────────────────┘ │ ▼ ┌─────────────────────────┐ │ String Pool (Heap) │ │ "Siraj" │ └─────────────────────────┘
This is how memory evolves step by step in Java 8+



Example: Showing how different types of variables (Local, Instance, Static, String, Primitives) are stored in Java 8+ JVM Memory Model

We’ll use a simple Employee class:

class Employee { static String company = "OpenAI";     // static variable String name;                 // instance variable (String) int age;                     // instance variable (primitive) }

And the main method:

public class Test { public static void main(String[] args) { int x = 10;                     // local primitive Employee emp = new Employee(); // reference in stack, object in heap emp.name = "Alice";             // String from pool emp.age = 30;                 // primitive instance variable } }


Step 1: Program Starts – Classes Loaded
  • Employee and Test classes are loaded into Metaspace.
  • Static variable company = "OpenAI" is created and stored in Heap (Class area inside heap, with static fields).
  • "OpenAI" literal is placed in the String Pool (inside Heap).

Stack (main) Heap (Objects + String Pool) Metaspace ┌───────────────┐ ┌───────────────────────────────┐ ┌───────────────────────────────┐ │ │ │ String Pool: │ │ Class metadata: │ │ │ │ "OpenAI" │ │ Employee, Test │ │ │ └───────────────────────────────┘ │ │ └───────────────┘ │ Static Variable:          | └───────────────────────────────┘ │ Employee.company ───►"OpenAI" └───────────────────────────────┘


Step 2: Local Variable Created

int x = 10; → stored in the stack frame of main().
Stack (main) Heap (Objects + String Pool) Metaspace ┌───────────────┐ ┌───────────────────────────────┐ ┌───────────────────────────────┐ │ x = 10 │ │ String Pool: │ │ Class metadata: │ │ │ │ "OpenAI" │ │ Employee, Test │ └───────────────┘ └───────────────────────────────┘ └───────────────────────────────┘ │ Static Variable:             | │ Employee.company ───►"OpenAI" └───────────────────────────────┘


Step 3: Object Creation

Employee emp = new Employee();

  • Reference emp stored in stack.
  • New Employee object created in heap with default values (name = null, age = 0).
Stack (main) Heap (Objects + String Pool) Metaspace ┌─────────────────────┐ ┌───────────────────────────────┐ ┌───────────────────────────────┐ │ x = 10 │ │ Employee Object │ │ Class metadata: │ │ emp ────────────► │────►│ name = null │ │ Employee, Test │ └─────────────────────┘ │ age = 0 │ └───────────────────────────────┘ └───────────────────────────────┘ │ Static Variable:             | │ Employee.company ───►"OpenAI"                               |String Pool:                 |"OpenAI"                   | └───────────────────────────────┘


Step 4: Assign Fields

emp.name = "Alice"; emp.age = 30;

  • "Alice" is stored in the String Pool (heap).
  • name now points to "Alice".
  • age is set to 30 directly in the heap object.
Stack (main) Heap (Objects + String Pool) Metaspace ┌─────────────────────┐ ┌───────────────────────────────┐ ┌───────────────────────────────┐ │ x = 10 │ │ Employee Object │ │ Class metadata: │ │ emp ────────────► │────►│ name ───────────► "Alice" │ │ Employee, Test │ └─────────────────────┘ │ age = 30 │ └───────────────────────────────┘ └───────────────────────────────┘ │ Static Variable:             | │ Employee.company ───►"OpenAI"                               |String Pool:                 |"OpenAI"                   |"Alice"                    | └───────────────────────────────┘
👉 Summary (Java 8+ JVM Memory Allocation): 
  • Local variables (x, emp) → stored in stack frame.
  • Instance variables (name, age) → stored inside the heap object.
  • Static variables (company) → stored in Heap (class area, once per class).
  • String literals ("OpenAI", "Alice") → stored in String Pool (inside Heap).
  • Class metadata (Employee, Test) → stored in Metaspace. 


Example: In Java (any version, including 8+), a static variable belongs to the class, not the object. So,

  • If you create two (or more) objects of Employee, you will still have only one memory location for static String company
  • Both objects share the same static variable
  • If one object changes it, the change is visible to all other objects (and even without objects, since it’s tied to the class).

Here is how two objects of employee class look like in JVM Memory Model,

class Employee { static String company = "Microsoft"; // static String name; // instance int age; // instance } public class Test { public static void main(String[] args) { Employee e1 = new Employee(); Employee e2 = new Employee(); e1.name = "Alice"; e2.name = "Bob"; System.out.println(e1.company); // Microsoft System.out.println(e2.company); // Microsoft // Change static through one object e1.company = "Google"; System.out.println(e1.company); // Google System.out.println(e2.company); // Google System.out.println(Employee.company); // Google } }

JVM Memory View (Java 8+)

Stack (main) Heap (Objects + Static + String Pool) Metaspace ─────────────────── ─────────────────────────────────────── ─────────────────────────── e1 ─────────────► Employee Object 1 Class metadata: e2 ─────────────► ┌───────────────────────────┐ - Employeename = "Alice" - Testage = 30 │ └───────────────────────────┘ Employee Object 2 ┌───────────────────────────┐ │ name = "Bob" │ │ age = 25 │ └───────────────────────────┘ Static Variable (shared across all): - Employee.company ───► "Google" String Pool: - "Microsoft" - "Google" - "Alice" - "Bob"
So in this way only one static variable memory is created, no matter how many objects are created. 



Example: Local Variables in Java 8+ JVM Memory Model

Definition: Local variables are variables declared inside a methodconstructor, or block.

Key Rules:

  1. Stored in the stack, inside the stack frame of the method.
  2. Lifetime = duration of method execution; removed when the method returns.
  3. Each method call gets a new copy of the variable.
  4. If the variable refers to an object, the reference is in the stack, but the object itself is in the heap.

Example 1: Simple Local Variable


public class Test {

    public static void main(String[] args) {

        int x = 10;      // local primitive

        System.out.println(x);

    }

}


Memory Model:


Stack (main method frame)

x = 10


Heap:
(empty, no objects created)


Explanation:

  • x is stored in the stack, inside the main method frame.
  • Once main finishes execution, the stack frame is destroyed, and x disappears.
  • No heap memory is used because x is a primitive


Example 2: Two Calls to a Method


public class Test {

    public static void printX() {

        int x = 100;  // local variable

        System.out.println(x);

    }


    public static void main(String[] args) {

        printX();  // first call

        printX();  // second call

    }

}


Memory Model:


Stack (Thread main)

───────────────────────────────

Frame: printX (first call)

  x = 100

───────────────────────────────

Frame: printX (second call)

  x = 100

───────────────────────────────


Heap:
(empty, no objects created)


Explanation:

  1. First call to printX()
    • JVM creates a stack frame for printX.
    • Local variable x = 100 is stored inside this frame.
  2. Second call to printX()
    • JVM creates another stack frame for this call.
    • This frame has its own copy of x = 100, independent of the first call.
  3. After each method returns
    • The corresponding stack frame is removed.
    • Local variables cease to exist.
  4. Heap:
    • No objects are created, so the heap remains empty.
  5. Thread Safety:
    • Each thread has its own stack.
    • Local variables in different threads do not interfere.

Key Takeaways

  1. Local variables live in the stack.
  2. Primitives (int, double, boolean, etc.) are stored directly in the stack.
  3. Each method call gets a new stack frame, so local variables are independent for each call.
  4. Thread-safe: each thread has its own stack; local variables in one thread do not affect others. 


JRE (Java Runtime Environment) Internals

The JRE provides everything needed to run Java apps (but not develop). It includes:

  • JVM (execution engine).
  • Core Libraries (java.lang, java.util, etc.).
  • Security & Config (policies, certificates).
  • Deployment (legacy) like Applets/Web Start.
  • JVMTI (debugging interface).
|----------------------------------------------------| | Deployment Technologies           |
|                                           | | - Java Web Start (JNLP, legacy) | | - Java Plug-in (Applet support, legacy) | |----------------------------------------------------| | Security & Config Layer            |
|                                         | | - java.policy (permissions) | | - cacerts (certificates) | | - Cryptographic providers | |----------------------------------------------------| | Core Java Libraries              |
|                                       | | - java.lang (String, Object, Math, Thread) | | - java.util (Collections, Date/Time) | | - java.io / java.nio (File I/O, streams) | | - java.net (Networking, HTTP, sockets) | | - java.sql (JDBC) | | - java.security / javax.crypto (Security, SSL) | | - Many more (XML, reflection, regex, etc.) | |----------------------------------------------------| | Java Virtual Machine (JVM)             |
|                                         | | - ClassLoader subsystem | | - Runtime Data Areas (Heap, Stack, PC, etc.) | | - Execution Engine (Interpreter + JIT + GC) | | - JNI, JVMTI | |----------------------------------------------------|
👉 Think of JRE as: JVM + Libraries + Config.
👉 It lets you run Java programs.


👉 Note (Java 9+): The JRE isn’t a separate install anymore; the runtime is modular. You can build custom runtimes using jlink.


JDK (Java Development Kit) Internals

The JDK = JRE + Developer ToolsIt includes everything in JRE plus:
  • Compiler (javac) → .java → .class.
  • Tools → jdb, jconsole, jvisualvm, etc.
  • Packagers → jar, jlink, jmod.
  • Docs → javadoc, javap.
|----------------------------------------------------| | Development Tools (JDK only)          |
|                                           | | - Compiler: javac, jar, jmod, jlink | | - Debugging: jdb, jconsole, jvisualvm, jmap, ... | | - Documentation: javadoc, javap | | - Deployment: jdeps, packaging tools | |----------------------------------------------------| | JRE (part of JDK)                      |
|                                | | - JVM (ClassLoader, Runtime Areas, JIT, GC, JNI) | | - Core Libraries (java.lang, java.util, etc.) | | - Security & Config (cacerts, policies, crypto) | | - Deployment Tech (Web Start/Applets, legacy) | |----------------------------------------------------| | Module System (Java 9+)             |
|                                         | | - jmods (modular runtime components) | | - jlink (create custom runtime images) | |----------------------------------------------------|

Final Recap

  • JVM = The engine (executes bytecode, manages memory, GC, JIT).
  • JRE = JVM + Libraries + Config (runtime to run apps).
  • JDK = JRE + Development tools (for developers).

In memory:

  • Class info → Method Area (Metaspace).
  • Objects → Heap.
  • References → Stack.
  • String literals → String Pool (inside Heap, not Metaspace).

👉 So,

  • End-user who only runs apps → needs JRE.
  • Developer who writes & runs apps → needs JDK.
  • JVM itself is invisible, but it’s the actual engine inside both.


JVM vs JRE vs JDK (Quick Comparison)

Feature / Component

JVM (Java Virtual Machine)

JRE (Java Runtime Environment)

JDK (Java Development Kit)

Purpose

Executes Java bytecode

Provides environment to run Java programs

Provides environment + tools to develop Java programs

Includes

Execution engine (Interpreter, JIT, GC)

JVM + Core Libraries + Config + Security

JRE + Compiler + Debuggers + Dev Tools

Developer Tools?

No

No

Yes (javac, jdb, jconsole, etc.)

Can run programs?

Yes

Yes

Yes

Can compile programs?

No

No

Yes

Memory Management

Handles Heap, Stack, PC Register, Metaspace, GC

Uses JVM for memory + provides libraries

Uses JRE + adds dev tools

Installation (Java 9+)

Comes with JRE/JDK

Merged into JDK (not standalone)

Main distribution (contains everything)

Typical Users

Internal runtime engine

End-users who just run Java apps

Developers writing and running Java apps


Layered Diagram: JVM vs JRE vs JDK 
┌──────────────────────────┐ │ JDK │ │ (Development Kit) │ │ │ │ - Compiler (javac) │ │ - Debuggers (jdb, etc.) │ │ - Profilers (jconsole) │ │ - Packaging tools │ │ - Docs (javadoc, javap) │ └───────────▲──────────────┘ │ ┌───────────┴──────────────┐ │ JRE │ │ (Runtime Environment) │ │ │ │ - Core Java Libraries │ │ - Security (policies) │ │ - Config (certs, props) │ │ - Deployment (legacy) │ └───────────▲──────────────┘ │ ┌───────────┴──────────────┐ │ JVM │ │ (Execution Engine) │ │ │ │ - ClassLoader │ │ - Runtime Data Areas │ │ (Heap, Stack, PC, │ │ Metaspace, etc.) │ │ - Execution Engine │ │ (Interpreter, JIT, GC)│ │ - JNI & Native Libs │ └──────────────────────────┘