JVM Internals

Arsh GhaiwatArsh Ghaiwat
5 min read

πŸš€ What Is the JVM and Why Should You Care?

The Java Virtual Machine (JVM) is what makes Java "write once, run anywhere." When you compile a .java file, Java doesn’t produce platform-specific machine code. Instead, it creates bytecode, which is understood by the JVM.

The JVM then interprets or compiles this bytecode into native machine code based on your system β€” whether it's Windows, macOS, or Linux.

But JVM does much more than just run your code. It:

  • Loads compiled classes into memory

  • Allocates memory for variables and objects

  • Runs your methods

  • Collects garbage (unused memory)

  • Supports multithreading and concurrency


πŸ”„ What Happens When You Run a Java Program?

Let’s walk through a simple example:

public class HelloJVM {
    public static void main(String[] args) {
        System.out.println("Hello, JVM!");
    }
}

When you run this code using javac and java, here’s what happens internally:

  1. The .java file is compiled into a .class file containing bytecode.

  2. The ClassLoader loads this bytecode into memory.

  3. The Bytecode Verifier checks it for security issues.

  4. The Execution Engine runs it line by line or compiles it just-in-time (JIT).

  5. Memory is allocated for variables and objects in the Stack and Heap.


🧱 JVM Architecture at a Glance

To understand how the JVM does all this, let’s look at its main components.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    Class Loader             β”‚ ← Loads bytecode (.class files)
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Runtime Memory Areas       β”‚ ← Stack, Heap, Method Area, etc.
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Execution Engine           β”‚ ← Interpreter + JIT Compiler
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Native Interface (JNI)     β”‚ ← Calls to C/C++ native code
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

These components work together like a well-oiled machine to keep your program running smoothly.


πŸ“₯ ClassLoader β€” The Entry Point

The ClassLoader is responsible for loading all your .class files (compiled bytecode) into memory. It works in a hierarchy β€” starting with the core Java classes and moving outward to user-defined ones.

  • Bootstrap ClassLoader loads core Java classes (like java.lang.*).

  • Extension ClassLoader loads JDK-provided libraries.

  • Application ClassLoader loads your app's classes from the classpath.

Once classes are loaded, they’re ready to be executed by the JVM.


🧠 Runtime Memory Areas β€” Where Java Lives

The JVM divides its memory into different regions for different purposes. The two most important areas for developers are the Stack and the Heap.

Let’s take a look at them one by one.


πŸ”Ή Stack Memory β€” Method-Level Storage

Each time you call a method, a new stack frame is created to store its parameters and local variables. When the method finishes, its frame is removed automatically.

This is why the stack is fast and doesn’t require manual cleanup.

public class StackExample {
    public static void main(String[] args) {
        int a = 5;
        int b = 10;
        int result = add(a, b);
    }

    static int add(int x, int y) {
        int sum = x + y;
        return sum;
    }
}

In the above example:

  • a, b, and result live in the main() stack frame

  • x, y, and sum live in the add() stack frame

When add() returns, its frame is popped off the stack.


πŸ”Ή Heap Memory β€” Object-Level Storage

The heap is where all Java objects are stored when you use new. Unlike stack memory, the heap is shared across all threads and is managed by the Garbage Collector.

public class HeapExample {
    public static void main(String[] args) {
        Student s1 = new Student("Alice");
        Student s2 = new Student("Bob");
    }
}

class Student {
    String name;
    Student(String name) {
        this.name = name;
    }
}

In this code:

  • s1 and s2 are references stored in the stack.

  • The actual Student objects live in the heap.


πŸ“Š Memory Layout Example

To tie everything together, here’s a visual layout of how memory looks during execution:

Thread-1 Stack         Thread-2 Stack
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Method A   β”‚         β”‚ Method X   β”‚
β”‚ Method B   β”‚         β”‚ Method Y   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

                Shared Heap
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚ new Student()      β”‚
          β”‚ new Book()         β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each thread has its own stack (used for method calls), but all objects are stored in a common heap space.


βš™οΈ Execution Engine β€” The Code Runner

Once memory is ready, the Execution Engine runs your bytecode.

It uses two main components:

  • Interpreter: Reads and executes bytecode line-by-line.

  • JIT Compiler (Just-In-Time): Compiles frequently used (hot) code into native machine code for faster execution.

Initially, the interpreter handles everything, but over time, the JIT takes over for performance.


🧹 Garbage Collection β€” The JVM’s Janitor

Objects created in the heap are automatically cleaned up when they’re no longer referenced. This process is called Garbage Collection (GC).

You don’t need to delete objects manually. JVM handles it smartly behind the scenes.

Person p = new Person();  // allocated in heap
p = null;                 // becomes eligible for GC

GC works in two main regions:

  • Young Generation: For new objects (frequent GC)

  • Old Generation: For long-lived objects (less frequent GC)


βœ… Recap: JVM Components & Their Roles

ComponentResponsibility
ClassLoaderLoads .class files into memory
Stack MemoryStores method calls and local variables (per thread)
Heap MemoryStores all objects and arrays (shared)
Execution EngineInterprets or compiles bytecode
Garbage CollectorReclaims memory of unused objects

πŸ’‘ Why JVM Internals Matter to You

Knowing how the JVM works is not just academic. It can help you:

  • Fix memory-related issues like StackOverflowError or OutOfMemoryError

  • Write more efficient, memory-safe code

  • Understand how threads behave

  • Prepare for coding interviews

  • Use tools like VisualVM or JConsole for performance tuning


🏁 Conclusion

The JVM is more than just a black box β€” it’s a powerful, layered engine that handles everything from loading your classes to managing memory and cleaning up unused data.

Now that you know:

  • What happens when Java runs

  • How memory is managed (Stack vs Heap)

  • What the Execution Engine and GC do

You’re better equipped to write better, faster, and smarter Java code.

0
Subscribe to my newsletter

Read articles from Arsh Ghaiwat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arsh Ghaiwat
Arsh Ghaiwat

Associate System Engineer | Currently learning Java & building fun side projects | Blogging my dev journey to revise, reflect, and grow πŸ‘¨β€πŸ’»πŸš€