Fundamentals 17 min read

Why Understanding JVM Memory Structure Is Crucial for Java Developers

This article explains the purpose of learning the JVM memory layout, describes each runtime data area—including program counter, VM stack, native method stack, heap, and method area—highlights thread‑private versus shared regions, and provides code samples that trigger StackOverflowError and OutOfMemoryError across different JDK versions.

Java Interview Crash Guide
Java Interview Crash Guide
Java Interview Crash Guide
Why Understanding JVM Memory Structure Is Crucial for Java Developers

Purpose of Understanding JVM Memory Structure

In Java development, the JVM automatically manages memory, so developers rarely need to manually free objects as in C/C++. However, when memory leaks or overflow occur, understanding how the JVM uses memory and its internal structure is essential for diagnosing and fixing the root cause.

JVM Memory Structure Overview

The JVM divides its managed memory into several runtime data areas:

Program Counter: a per‑thread indicator of the current bytecode line.

Virtual Machine Stack: per‑thread memory model for Java method execution, storing local variables, operand stack, dynamic linking, and method return information.

Native Method Stack: similar to the VM stack but serves native (C/C++) methods used by the JVM.

Heap: the largest shared memory area, used to store object instances and managed by the garbage collector.

Method Area: shared memory for class metadata, constants, static variables, and JIT‑compiled code.

The program counter, VM stack, and native method stack are thread‑private (yellow region), while the heap and method area are shared among threads (red region). The article analyzes each area in detail.

Program Counter

The Program Counter Register (PC) is a small memory space that records the line number of the bytecode currently executed by a thread. The bytecode interpreter updates this value to select the next instruction, enabling branching, loops, exception handling, and thread resumption. Each thread has its own PC, making it a thread‑private memory region. When a thread executes a Java method, the PC points to the bytecode address; for native methods, the PC is undefined. The JVM specification does not define any OutOfMemoryError for this area.

Virtual Machine Stack

The VM stack is also thread‑private. Each thread creates its own stack when it starts and destroys it when the thread ends. Every method invocation creates a stack frame that stores the local variable table, operand stack, dynamic linking information, and method return data. The local variable table holds primitive types, object references, and return addresses. Long and double values occupy two slots; other types occupy one. The size of the local variable table is fixed at compile time and does not change at runtime.

The JVM specification defines two error conditions for this area:

1. If a thread requests a stack depth greater than the allowed maximum, a StackOverflowError is thrown. Example code:

/**
 * VM Args: -Xss128k
 */
public class JVMStackSOF {
    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        JVMStackSOF sof = new JVMStackSOF();
        try {
            sof.stackLeak();
        } catch (Throwable e) {
            System.out.println("Stack length:" + sof.stackLength);
            throw e;
        }
    }
}

Running with -Xss128k produces a StackOverflowError when the stack depth reaches 1002.

2. If the VM stack can grow but cannot obtain enough native memory, an OutOfMemoryError is thrown. Example code:

/**
 * VM Args: -Xss2M
 */
public class JVMStackOOM {
    private void dontStop() {
        while (true) {
        }
    }

    public void stackLeakByThread() {
        while (true) {
            Thread t = new Thread(new Runnable() {
                public void run() {
                    dontStop();
                }
            });
            t.start();
        }
    }

    public static void main(String[] args) {
        JVMStackOOM oom = new JVMStackOOM();
        oom.stackLeakByThread();
    }
}

Running with -Xss2M eventually throws an OutOfMemoryError: unable to create new native thread because the OS cannot allocate more native threads.

Native Method Stack

The Native Method Stack serves native methods similarly to how the VM stack serves Java methods. Some JVMs (e.g., HotSpot) merge the native stack with the VM stack. This area can also throw StackOverflowError and OutOfMemoryError.

Heap

The Java heap is the largest memory area, shared by all threads and created at JVM startup. It stores object instances and is the primary region managed by the garbage collector. Modern collectors use generational algorithms, dividing the heap into young and old generations, with further subdivisions such as Eden, From Survivor, and To Survivor spaces. The heap may consist of multiple thread‑local allocation buffers (TLABs). The heap can be fixed‑size or expandable (controlled by -Xmx and -Xms).

If the heap cannot satisfy an allocation and cannot expand, an OutOfMemoryError is thrown. Example code:

/* VM Args: -Xms20M -Xmx20M */
public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            // keep objects referenced so they are not GC'd
            list.add(new OOMObject());
        }
    }
}

Running with -Xms20M -Xmx20M results in java.lang.OutOfMemoryError: Java heap space.

Method Area

The Method Area, like the heap, is shared among threads and stores loaded class metadata, constants, static variables, and JIT‑compiled code. In HotSpot, this area was historically called the “Permanent Generation” (PermGen). Starting with JDK 8, PermGen was removed and replaced by Metaspace, which is allocated in native memory.

The JVM imposes few limits on this area; however, when it cannot satisfy memory requests, an OutOfMemoryError occurs. Example code that exhausts the method area by dynamically generating classes:

/* VM Args: -XX:PermSize=2M -XX:MaxPermSize=2M */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 100000; i++) {
            System.out.println(i);
            // intern the string to place it in the runtime constant pool
            list.add(String.valueOf(i).intern());
        }
    }
}

Running on JDK 6 with the above VM arguments throws java.lang.OutOfMemoryError: PermGen space. On JDK 7 the same code completes without error because the string constant pool was moved out of PermGen. On JDK 8 the PermSize/MaxPermSize options are ignored, and Metaspace is used instead.

Another example that loads many generated classes:

public class MethodAreaOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        for (int i = 0; i < 300; i++) {
            System.out.println(i);
            createNewClass();
        }
    }

    private static void createNewClass() {
        // Use CGLIB to dynamically create a class, loading it into the method area
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OOMObject.class);
        enhancer.setUseCache(false);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                return proxy.invokeSuper(obj, args);
            }
        });
        enhancer.create();
    }
}

Running with -XX:PermSize=2M -XX:MaxPermSize=2M on JDK 6 throws OutOfMemoryError: PermGen space. On JDK 7 it also throws an OOM, while on JDK 8 the options are ignored and no error occurs.

Summary

In the JVM, memory is divided into:

Program Counter : per‑thread bytecode line indicator.

Virtual Machine Stack : per‑thread model for Java method execution, storing local variables, operand stack, etc.

Native Method Stack : per‑thread model for native method execution.

Heap : shared area for object instances, managed by the garbage collector.

Method Area : shared area for class metadata, constants, static variables, and JIT code (PermGen/Metaspace).

Understanding these regions helps developers diagnose StackOverflowError and OutOfMemoryError issues and adapt to differences across JDK versions.

JavaJVMOutOfMemoryErrorStackOverflowError
Java Interview Crash Guide
Written by

Java Interview Crash Guide

Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.