CodeGym /Courses /JAVA 25 SELF /Memory layout in the JVM: stack, heap, PermGen/Metaspace

Memory layout in the JVM: stack, heap, PermGen/Metaspace

JAVA 25 SELF
Level 64 , Lesson 0
Available

1. Overview of a Java process’s memory

When you run a Java program, the JVM (Java Virtual Machine) asks the operating system for a chunk of memory. Sometimes it’s modest, sometimes it’s quite large (especially if you launch something like Minecraft with tons of mods). This memory is divided into several key areas, each playing its own role:

  • Stack — for local variables and method calls.
  • Heap — for all objects you create with new.
  • Service areas (PermGen/Metaspace) — for class metadata, static fields, and other “magical” things.

It looks roughly like this:

┌───────────────────────────────┐
│ JVM process                   │
│ ┌─────────────┐               │
│ │  Stack      │ ← Each thread has its own stack!
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Shared by all threads
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ PermGen/      │ ← Class metadata
│ │ Metaspace     │
│ └───────────────┘             │
└───────────────────────────────┘

Why is this important?

  • Understanding the memory layout helps you write more efficient and safer code.
  • It’s easier to diagnose errors like StackOverflowError or OutOfMemoryError.
  • “Garbage collector” and “memory leak” stop being scary words — you understand where and what to look for.

2. Stack: fast, local, but not forever

The stack is a special memory area allocated per thread. The stack is like a pile of plates: the last one you put on is the first you can take off, but only after you remove those above it. In other words, the stack works on a LIFO (Last In, First Out) principle.

What is the stack for?

The stack stores:

  • Local variables of methods (for example, int x = 5; inside a method).
  • Return address after a method call (to know where to return after the method finishes).

Every time you call a method, a new frame (stack frame) is pushed onto the stack — essentially a box that contains all the local variables of that method and service information. When the method completes, its frame is popped — its local variables disappear.

Example

public static void main(String[] args) {
    int a = 10;           // a is on main's stack frame
    int b = sum(a, 5);    // call sum
}

public static int sum(int x, int y) {
    int result = x + y;   // x, y, result are on sum's stack frame
    return result;
}
  • When sum is called, a separate frame is created on the stack for it.
  • After sum finishes, its variables disappear.

Variable lifecycle

Local variables live only as long as the method in which they’re declared is running. As soon as the method returns, they’re gone and the memory is freed immediately.

Stack overflow

If you accidentally (or intentionally) write infinite recursion, each method call will add a new frame to the stack. At some point the stack will run out, and you’ll get:

Exception in thread "main" java.lang.StackOverflowError

Example:

public static void main(String[] args) {
    recurse();
}
public static void recurse() {
    recurse(); // Infinite recursion!
}

Stack size

The stack size is limited — typically a few megabytes per thread (it can be set with -Xss). If the stack is exhausted, the program crashes with an error.

3. Heap: a place for your objects

The heap is a shared memory area for all threads where all objects you create with new, as well as arrays, live. This is where all the magic of object-oriented programming happens.

How do objects end up in the heap?

String s = new String("Hello");
int[] arr = new int[10];
  • The variable s is a reference; it lives on the stack.
  • The String object itself and the array arr live in the heap.

Object lifecycle

An object lives in the heap as long as there is at least one strong reference to it. As soon as nothing references the object, it becomes “garbage” and may be removed by the garbage collector (GC).

Memory management

Unlike C/C++, where you must free memory yourself (free, delete), in Java this is handled by the GC. You cannot explicitly free an object, but you can null out all references to it — then it becomes a candidate for collection.

Diagram: where does what live?

Stack (main)
  └─ s ─┬────────────┐
         │           │
         ▼           │
      Heap           │
   ┌─────────────┐   │
   │ String "Hello"◄──┘
   └─────────────┘

Heap specifics

  • There is a single heap for the entire JVM process.
  • You can set the heap size at startup (-Xmx, -Xms).
  • If there is no free space left in the heap and the GC cannot free memory, the program crashes with OutOfMemoryError.

4. PermGen and Metaspace: where do classes live?

When you write class MyClass { ... } and then run the program, the JVM has to store everything related to this class somewhere — methods, fields, bytecode, static variables, constants, and even string literals. For this, the JVM has a special memory area where classes “live.”

In the past, before Java 8, this area was called PermGen (Permanent Generation). But it had plenty of issues — for example, it had a fixed size, and if it ran out, the application simply crashed with OutOfMemoryError: PermGen space.

With Java 8, a new, more flexible area appeared — Metaspace. It replaced the old PermGen and can now grow automatically, taking as much memory as the system needs (within available physical memory).

PermGen (pre–Java 8)

  • PermGen stored class metadata, static fields, and string literals.
  • The size of PermGen was limited (small by default); you could increase it with -XX:MaxPermSize=256m.
  • If an application loaded lots of classes dynamically (for example, in web servers), PermGen could “run out,” and you’d see an error:
java.lang.OutOfMemoryError: PermGen space
  • The problem: PermGen cleanup didn’t always happen correctly when classes were unloaded dynamically (for example, during web application reloads).

Metaspace (Java 8+)

  • Starting with Java 8, PermGen disappeared and Metaspace was introduced.
  • Metaspace stores class metadata, but now in native memory (outside the Java heap).
  • By default, Metaspace is not limited (it’s only limited by system memory), but you can set a cap with -XX:MaxMetaspaceSize=512m.
  • The error when memory runs out now looks like this:
java.lang.OutOfMemoryError: Metaspace
  • Metaspace also holds static fields, methods, and class information.

Diagram: how it’s all arranged

┌───────────────────────────────┐
│ JVM process                   │
│ ┌─────────────┐               │
│ │  Stack      │ ← Local variables, method calls
│ └─────────────┘               │
│ ┌─────────────┐               │
│ │  Heap       │ ← Objects, arrays, everything created via new
│ └─────────────┘               │
│ ┌───────────────┐             │
│ │ Metaspace     │ ← Class metadata, static fields
│ └───────────────┘             │
└───────────────────────────────┘

Why does this matter?

If you write typical desktop or server applications, you’ll most likely never hit PermGen or Metaspace errors. But if you work with dynamic class loading (for example, plugins, web applications, frameworks like Spring that can load and unload lots of classes), then knowing about Metaspace is a must-have!

5. Illustration: JVM memory diagram

flowchart TD
    subgraph JVM
        direction TB
        Stack1["Stack (Thread 1)"]
        Stack2["Stack (Thread 2)"]
        Heap[Heap]
        Metaspace[Metaspace]
    end
    Stack1 --references--> Heap
    Stack2 --references--> Heap
    Heap --uses classes from--> Metaspace
  • Each thread has its own stack.
  • All stacks can reference objects in the heap.
  • Objects in the heap “know” their class, whose information resides in Metaspace.

6. Example: what this looks like in real code

public class MemoryDemo {
    public static void main(String[] args) {
        int x = 42; // x lives on main's stack frame
        String s = "Hello!"; // s is a reference on the stack, the String object is on the heap, the literal "Hello!" is in Metaspace
        Person p = new Person("Alice"); // p is a reference on the stack, the Person object is on the heap

        // Call a method to create a new stack frame
        printPerson(p);
    }

    public static void printPerson(Person person) {
        // person is a reference on printPerson's stack frame
        System.out.println(person.getName());
    }
}

class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() { return name; }
}

Walkthrough:

  • x is a local variable that lives on the main method’s stack frame.
  • s is a reference on the stack, the String object is on the heap, and the string literal "Hello!" is in Metaspace.
  • p is a reference on the stack, the Person object is on the heap.
  • The Person class and all its methods/fields are in Metaspace (class metadata).
  • The call printPerson(p) creates a new stack frame; inside it, the local reference person points to the same object in the heap.

7. How the JVM manages memory: a brief FAQ

Can I manage the stack?
No, the stack is fully controlled by the JVM. You can only set its size at startup (-Xss).

Can I manage the heap?
Partially: the heap size is set at startup (-Xmx, -Xms). Cleanup is handled by the garbage collector (GC).

Can I manage Metaspace?
You can limit its size (-XX:MaxMetaspaceSize), but this is usually unnecessary.

What happens when memory runs out?
— If the stack runs out — StackOverflowError.
— If the heap runs out — OutOfMemoryError: Java heap space.
— If Metaspace runs out — OutOfMemoryError: Metaspace.

8. Common memory-related mistakes

Error #1: StackOverflowError due to infinite recursion. The most common cause is forgetting to provide a recursion exit condition. For example, a method calls itself endlessly. The JVM cannot grow the stack indefinitely, and the program will crash.

Error #2: OutOfMemoryError due to heap exhaustion. If you create too many objects that remain referenced by variables/collections (for example, you keep adding items to a list and never remove them), the heap can be exhausted.

Error #3: OutOfMemoryError: PermGen space / Metaspace. If you use plugins or dynamically load lots of classes and Metaspace is not cleaned up (for example, due to improper class unloading), Metaspace can run out of space.

Error #4: Confusing a reference with an object. Many beginners confuse this: a variable of type Person on the stack is only a reference, while the object itself lives in the heap.

Error #5: Expecting the garbage collector to delete everything instantly. The GC works “when it wants to” (in reality — based on internal algorithms and memory pressure), not immediately after you null a reference. Don’t count on instant memory release.

1
Task
JAVA 25 SELF, level 64, lesson 0
Locked
StackOverflowError – Playful Cascade of Recursion 🧙‍♂️
StackOverflowError – Playful Cascade of Recursion 🧙‍♂️
1
Task
JAVA 25 SELF, level 64, lesson 0
Locked
OutOfMemoryError – Insatiable Memory Eater 💾
OutOfMemoryError – Insatiable Memory Eater 💾
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION