← Back to list

Understanding Java Memory: A Deep Dive into Stack, Heap, and Metaspace

If you’ve ever wondered where Java stores your variables, objects, and method calls, you’re not alone. Understanding Java’s memory model —…

RamazanH1_ · 2026-02-02 22:33 · 2 claps · 14.2 min read
#java-memory-model #stack #heap #metaspace
Open on Medium ↗
Wiki topics: 🔭 · Astronomy & Space

Understanding Java Memory: A Deep Dive into Stack, Heap, and Metaspace

If you’ve ever wondered where Java stores your variables, objects, and method calls, you’re not alone. Understanding Java’s memory model — specifically the Stack, Heap, and Metaspace — is crucial for writing efficient, bug-free code and troubleshooting memory issues like StackOverflowError or OutOfMemoryError.

In this comprehensive guide, we’ll explore:

  • What Stack, Heap, and Metaspace memory are
  • What gets stored in each memory area
  • How static variables and methods are stored
  • How they work with real-world examples
  • Best practices for modern Java development

Table of Contents

  1. Java Memory Model Overview
  2. Stack Memory: The Fast Lane
  3. Heap Memory: The Storage Warehouse
  4. Metaspace: The Class Metadata Repository
  5. Static Variables and Methods: Where Do They Live?
  6. How Stack, Heap, and Metaspace Work Together
  7. Real-World Examples with Method Calls
  8. Common Memory Errors and How to Avoid Them

1. Java Memory Model Overview

When a Java application runs, the JVM (Java Virtual Machine) divides memory into several areas. The three most important for developers are:

  • Stack Memory: Fast, thread-specific storage for method execution
  • Heap Memory: Shared storage for all objects created during runtime
  • Metaspace: Native memory area for class metadata (introduced in Java 8)

Think of it like this:

  • Stack = Your desk workspace (small, organized, fast access)
  • Heap = Your warehouse (large, shared, needs management)
  • Metaspace = Your blueprint library (class definitions, static data, method code)
┌─────────────────────────────────────────────────────┐
│              JVM MEMORY STRUCTURE                    │
├─────────────────────────────────────────────────────┤
│  STACK (Thread-specific)                             │
│  - Method frames                                     │
│  - Local variables (primitives)                      │
│  - References to objects                             │
├─────────────────────────────────────────────────────┤
│  HEAP (Shared by all threads)                        │
│  - All objects (created with new)                    │
│  - Instance variables                                │
│  - Arrays                                            │
├─────────────────────────────────────────────────────┤
│  METASPACE (Native Memory)                           │
│  - Class metadata                                    │
│  - Static variables (references/primitives)          │
│  - Static methods (bytecode)                         │
│  - Constant pool                                     │
└─────────────────────────────────────────────────────┘

2. Stack Memory: The Fast Lane

What is Stack Memory?

Stack memory is a special region where Java stores:

  • Local variables (primitives like int, boolean, char)
  • Method call information (method parameters, return addresses)
  • References to objects (not the actual objects!)

Key Characteristics

┌───────────────────────┬─────────────────────────────────────────────────────────────────┐
│       FEATURE         │                         DESCRIPTION                             │
├───────────────────────┼─────────────────────────────────────────────────────────────────┤
│ Size                  │ Small and fixed (typically 512KB - 1MB per thread)              │
├───────────────────────┼─────────────────────────────────────────────────────────────────┤
│ Access                │ LIFO (Last In, First Out) - like a stack of plates             │
├───────────────────────┼─────────────────────────────────────────────────────────────────┤
│ Speed                 │ Very fast allocation and deallocation                           │
├───────────────────────┼─────────────────────────────────────────────────────────────────┤
│ Thread Safety         │ Each thread has its own stack                                   │
├───────────────────────┼─────────────────────────────────────────────────────────────────┤
│ Lifetime              │ Variables exist only while the method is executing              │
├───────────────────────┼─────────────────────────────────────────────────────────────────┤
│ Management            │ Automatically managed - no garbage collection needed            │
└───────────────────────┴─────────────────────────────────────────────────────────────────┘

What Gets Stored in Stack?

  1. Primitive Data Types
int age = 25;           // Stored in stack
double salary = 50000.0; // Stored in stack
boolean isActive = true; // Stored in stack
char grade = 'A';       // Stored in stack
  1. Object References (but not the actual object)
String name = "John";   // Reference stored in stack, object in heap
Person person = new Person(); // Reference in stack, Person object in heap
  1. Method Call Frames
  • Local variables
  • Method parameters
  • Return address

Speaking of Method Frames lets dive into how methods behave inside stack:

How Methods Behave in Stack Memory

Understanding how methods are executed and stored in the stack is crucial for grasping Java’s memory model. Let’s explore this in detail.

The Stack Frame Concept

Every time a method is called, the JVM creates a stack frame (also called an activation record) and pushes it onto the thread’s stack. This frame contains everything needed to execute that method.

What’s Inside a Stack Frame?

═══════════════════════════════════════════════════════════════════════
                         STACK FRAME CONTENTS
═══════════════════════════════════════════════════════════════════════

COMPONENT              │  DESCRIPTION
───────────────────────┼───────────────────────────────────────────────
Local Variable Array   │  Holds method parameters and local variables
───────────────────────┼───────────────────────────────────────────────
Operand Stack          │  Workspace for computations and operations
───────────────────────┼───────────────────────────────────────────────
Frame Data             │  Return address, exception handling info
───────────────────────┼───────────────────────────────────────────────
Return Value           │  Space to store method's return value

═══════════════════════════════════════════════════════════════════════

Method Execution Flow: Step-by-Step

Let’s see how the stack behaves with a practical example:

public class Calculator {

    public static void main(String[] args) {
        int a = 5;
        int b = 10;
        int result = add(a, b);
        System.out.println(result);
    }

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

Step 1: main() Method Starts

When main() is called, a stack frame is created:

STACK (Thread: main)
┌─────────────────────────────────┐
│  main() Stack Frame             │
│  ┌───────────────────────────┐  │
│  │ Local Variables:          │  │
│  │  - args = reference       │  │
│  │  - a = 5                  │  │
│  │  - b = 10                 │  │
│  │  - result = ? (undefined) │  │
│  └───────────────────────────┘  │
│  Return Address: (JVM entry)    │
└─────────────────────────────────┘

Step 2: add() Method Called

When add(a, b) is invoked, a NEW stack frame is pushed on top:

STACK (Thread: main)
┌─────────────────────────────────┐  ← TOP (Current)
│  add() Stack Frame              │
│  ┌───────────────────────────┐  │
│  │ Parameters:               │  │
│  │  - x = 5 (copy of a)      │  │
│  │  - y = 10 (copy of b)     │  │
│  │ Local Variables:          │  │
│  │  - sum = ? (undefined)    │  │
│  └───────────────────────────┘  │
│  Return Address: main() line 5  │
├─────────────────────────────────┤
│  main() Stack Frame             │
│  ┌───────────────────────────┐  │
│  │ Local Variables:          │  │
│  │  - args = reference       │  │
│  │  - a = 5                  │  │
│  │  - b = 10                 │  │
│  │  - result = ? (waiting)   │  │
│  └───────────────────────────┘  │
│  Return Address: (JVM entry)    │
└─────────────────────────────────┘

Important Notes:

✅ Parameters x and y are copies of a and b (pass by value)
✅ main() is still on the stack but paused (waiting for add() to return)
✅ The stack grows upward (newer frames on top)

Step 3: add() Executes

Inside add(), the computation happens:

STACK (Thread: main)
┌─────────────────────────────────┐
│  add() Stack Frame              │
│  ┌───────────────────────────┐  │
│  │ Parameters:               │  │
│  │  - x = 5                  │  │
│  │  - y = 10                 │  │
│  │ Local Variables:          │  │
│  │  - sum = 15 ✓ (computed) │  │
│  └───────────────────────────┘  │
│  Return Value: 15               │
│  Return Address: main() line 5  │
├─────────────────────────────────┤
│  main() Stack Frame (paused)    │
│  ...                             │
└─────────────────────────────────┘

Step 4: add() Returns

When add() finishes:

  1. The return value (15) is placed in a return slot
  2. The add() stack frame is popped (removed)
  3. Control returns to main()
  4. The return value is assigned to result
STACK (Thread: main)
┌─────────────────────────────────┐
│  main() Stack Frame             │
│  ┌───────────────────────────┐  │
│  │ Local Variables:          │  │
│  │  - args = reference       │  │
│  │  - a = 5                  │  │
│  │  - b = 10                 │  │
│  │  - result = 15 ✓          │  │
│  └───────────────────────────┘  │
│  Return Address: (JVM entry)    │
└─────────────────────────────────┘

(add() frame is completely removed)

Step 5: main() Completes

When main() finishes, its frame is also popped:

STACK (Thread: main)
┌─────────────────────────────────┐
│  (empty - thread terminates)    │
└─────────────────────────────────┘

Key Behaviors of Methods in Stack

1. LIFO (Last In, First Out) Principle

Method Call Sequence:    Stack Growth:
main()          →        ┌─────────┐
  └→ methodA()  →        │ methodC │ ← TOP (most recent)
      └→ methodB() →     ├─────────┤
          └→ methodC()   │ methodB │
                         ├─────────┤
                         │ methodA │
                         ├─────────┤
                         │ main()  │ ← BOTTOM (oldest)
                         └─────────┘

Return Sequence:         Stack Shrinkage:
methodC returns →        ┌─────────┐
methodB returns →        │ methodA │ ← methodC popped
methodA returns →        ├─────────┤
main() returns           │ main()  │ ← methodB popped
                         └─────────┘

2. Pass-by-Value for Primitives

public class PassByValueDemo {

    public static void main(String[] args) {
        int original = 100;
        modify(original);
        System.out.println(original);  // Still 100!
    }

    public static void modify(int value) {
        value = 999;  // Only changes the COPY
    }
}
---------------------------------------------------------------

STACK
┌──────────────────────────┐
│ modify() frame           │
│  - value = 999           │  ← Changed copy
├──────────────────────────┤
│ main() frame             │
│  - original = 100        │  ← Original unchanged
└──────────────────────────┘

3. Pass-by-Value for Object References

public class ReferenceDemo {

    public static void main(String[] args) {
        Person person = new Person("Alice");
        changeName(person);
        System.out.println(person.name);  // "Bob" - changed!
    }

    public static void changeName(Person p) {
        p.name = "Bob";  // Modifies the SAME object
    }
}

---------------------------------------------------------
STACK                           HEAP
┌──────────────────────┐       ┌─────────────────┐
│ changeName() frame   │       │ Person @0x100   │
│  - p = 0x100 ────────┼───────▶ name = "Bob"   │
├──────────────────────┤       └─────────────────┘
│ main() frame         │              ▲
│  - person = 0x100 ───┼──────────────┘
└──────────────────────┘

Key Point: The reference is copied (both point to same object), but the object itself lives in the heap.

4. Recursion and Stack Depth

Recursive methods create multiple stack frames:

public static int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// Call: factorial(4)

-----------------------------------------------------------------------
┌───────────────────────┐
│ factorial(4)          │  ← Current call
│  - n = 4              │
│  - waiting for result │
├───────────────────────┤
│ factorial(3)          │
│  - n = 3              │
├───────────────────────┤
│ factorial(2)          │
│  - n = 2              │
├───────────────────────┤
│ factorial(1)          │  ← Base case (returns 1)
│  - n = 1              │
│  - returns 1 ✓        │
├───────────────────────┤
│ main()                │
└───────────────────────┘

Stack Unwinding (Returning):
Step 1: factorial(1) returns 1
Step 2: factorial(2) returns 2 * 1 = 2
Step 3: factorial(3) returns 3 * 2 = 6
Step 4: factorial(4) returns 4 * 6 = 24

Danger: Too many recursive calls → StackOverflowError

// BAD: Infinite recursion
public static void infiniteRecursion() {
    infiniteRecursion();  // No base case!
}

// Stack keeps growing until:
// Exception in thread "main" java.lang.StackOverflowError

3. Heap Memory: The Storage Warehouse

What is Heap Memory?

Heap memory is where all Java objects and instance variables live. It’s shared across all threads in your application.

╔═══════════════════════╦═════════════════════════════════════════════════════════════════╗
║       FEATURE         ║                         DESCRIPTION                             ║
╠═══════════════════════╬═════════════════════════════════════════════════════════════════╣
║ Size                  ║ Large and configurable (can scale to many GBs)                  ║
╠═══════════════════════╬═════════════════════════════════════════════════════════════════╣
║ Access                ║ Random Access; objects accessible from anywhere                 ║
╠═══════════════════════╬═════════════════════════════════════════════════════════════════╣
║ Speed                 ║ Slower (overhead for allocation and fragmentation)              ║
╠═══════════════════════╬═════════════════════════════════════════════════════════════════╣
║ Thread Safety         ║ Shared; requires synchronization (locks/volatile)               ║
╠═══════════════════════╬═════════════════════════════════════════════════════════════════╣
║ Lifetime              ║ Global; objects persist until no longer referenced              ║
╠═══════════════════════╬═════════════════════════════════════════════════════════════════╣
║ Management            ║ Managed by the Garbage Collector (GC)                           ║
╚═══════════════════════╩═════════════════════════════════════════════════════════════════╝

Heap Structure (Generational)

The heap is divided into generations to optimize garbage collection:

┌─────────────────────────────────────────────────────────────────┐
│                          HEAP MEMORY                            │
├─────────────────────────────────────────────────────────────────┤
│        YOUNG GENERATION        │        OLD GENERATION          │
├──────────┬──────────┬──────────┤          (TENURED)             │
│          │ Survivor │ Survivor │                                │
│   Eden   │    S0    │    S1    │       Long-lived Objects       │
│          │          │          │                                │
└──────────┴──────────┴──────────┴────────────────────────────────┘

What Gets Stored in Heap?

  1. All Objects Created with new
String name = new String("John");    // String object in heap
Person person = new Person();         // Person object in heap
int[] numbers = new int[10];         // Array object in heap

2.Instance Variables (Object Fields)

public class Person {
    private String name;      // Stored in heap (with Person object)
    private int age;          // Stored in heap (with Person object)
    private Address address;  // Reference in heap, Address object also in heap
}

3.String Pool (Special area in heap)

String s1 = "Hello";  // "Hello" stored in String pool (heap)
String s2 = "Hello";  // Reuses same "Hello" from pool

4. Metaspace: The Class Metadata Repository

What is Metaspace?

Metaspace is a native memory region introduced in Java 8 that replaced the older PermGen (Permanent Generation). It stores class-level metadata and structural information about your application.

┌─────────────────────────────────────────────────────────────────┐
│                        NATIVE MEMORY                            │
├─────────────────────────────────────────────────────────────────┤
│                          METASPACE                              │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │                  CLASS METADATA STORAGE                   │  │
│  │  - Method Data       - Annotations      - Constant Pool   │  │
│  │  - Bytecode          - Field Data       - vTable          │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

What Gets Stored in Metaspace?

  1. Class Metadata
  • Class structure and definitions
  • Field descriptions
  • Method signatures
  1. Method Bytecode
  • Compiled bytecode for all methods (static and instance)
  • Method metadata

3.Static Variables

  • Primitive static values
  • References to static objects (actual objects in heap)

4.Constant Pool

  • Runtime constant pool per class
  • Symbolic references

5.JIT Compiled Code (partially)

  • Some optimized native code
JAVA 7 (PermGen)              JAVA 8+ (Metaspace)
┌────────────────┐            ┌────────────────┐
│ Fixed Size     │            │ Dynamic Size   │
│ Part of Heap   │   ──────▶  │ Native Memory  │
│ GC'd with Heap │            │ Auto-grows     │
│ Size: -XX:     │            │ Size: -XX:Max  │
│  MaxPermSize   │            │  MetaspaceSize │
└────────────────┘            └────────────────┘

Benefits of Metaspace:

  • No more OutOfMemoryError: PermGen space
  • Automatic resizing based on application needs
  • Better memory utilization
  • Only limited by available native memory

5. Static Variables and Methods: Where Do They Live?

Static Methods Storage

Static methods are stored in Metaspace as part of the class metadata:

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

Memory Layout:

METASPACE
┌─────────────────────────────────┐
│ Class: MathUtils                │
│ ┌─────────────────────────────┐ │
│ │ Method: add(int, int)       │ │
│ │ - Bytecode                  │ │
│ │ - Method signature          │ │
│ │ - Access modifiers          │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘

Key Points:

  • Static methods belong to the class, not instances
  • Bytecode and metadata stored in Metaspace
  • No object needed to call them
  • Loaded once when class is loaded

Static Variables Storage

Static variables have a more nuanced storage model:

Case 1: Static Primitive Variables

public class Counter {
    public static int count = 0;  // Primitive
}

------------------------------------------------------------

METASPACE
┌─────────────────────────────────┐
│ Class: Counter                  │
│ ┌─────────────────────────────┐ │
│ │ static int count = 0        │ │
│ │ (value stored here)         │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘

Storage: The value itself is stored in Metaspace.

Case 2: Static Object References

public class Database {
    public static Connection connection = new Connection();
}
-----------------------------------------------------------
METASPACE                       HEAP
┌───────────────────────┐      ┌─────────────────────────┐
│ Class: Database       │      │                         │
│ ┌───────────────────┐ │      │  Connection Object      │
│ │ static Connection │ │      │  @0x500                 │
│ │ connection=0x500  │─┼──────▶┌─────────────────────┐ │
│ └───────────────────┘ │      │ │ host = "localhost" │ │
└───────────────────────┘      │ │ port = 5432        │ │
                               │ └─────────────────────┘ │
                               └─────────────────────────┘

Storage:

  • The reference (pointer 0x500) is stored in Metaspace
  • The actual object is stored in Heap

Complete Example with Static Members:

public class Config {
    // Static primitive - value in Metaspace
    public static int MAX_CONNECTIONS = 100;

    // Static object reference - reference in Metaspace, object in Heap
    public static String APP_NAME = new String("MyApp");

    // Static array reference - reference in Metaspace, array in Heap
    public static int[] PORTS = {8080, 8081, 8082};

    // Static method - bytecode in Metaspace
    public static void configure() {
        System.out.println("Configuring...");
    }

    // Instance variable - stored in Heap with object
    private String environment;

    // Instance method - bytecode in Metaspace (shared by all instances)
    public void deploy() {
        System.out.println("Deploying...");
    }
}

--------------------------------------------------------------------------

METASPACE                                    HEAP
┌────────────────────────────────────┐      ┌──────────────────────────┐
│ Class: Config                      │      │                         │
│ ┌────────────────────────────────┐ │      │ String Object @0x100    │
│ │ static int MAX_CONNECTIONS=100│ │       │ "MyApp"                 │
│ │ static String APP_NAME = 0x100│─┼──────▶                         │ 
│ │ static int[] PORTS = 0x200    │─┼────┐  │                         │
│ │                               │ │    │ │ int[] Array @0x200       │
│ │ static void configure() {    │ │    └─▶[8080, 8081, 8082]         │
│ │   // bytecode                │ │      │                           │
│ │ }                             │ │      │ Config Object @0x300     │
│ │                               │ │      │ ┌──────────────────────┐ │
│ │ void deploy() {               │ │      │ │environment="prod"    │ │
│ │   // bytecode (shared)        │ │      │ └──────────────────────┘ │
│ │ }                             │ │      └──────────────────────────┘
│ └────────────────────────────────┘ │
└────────────────────────────────────┘
┌─────────────────────────────┬──────────────────────────────────┬─────────────────────────────┐
│           ITEM              │   REFERENCE/VALUE LOCATION       │      OBJECT LOCATION        │
├─────────────────────────────┼──────────────────────────────────┼─────────────────────────────┤
│ Static primitive            │ Metaspace (value)                │ N/A                         │
├─────────────────────────────┼──────────────────────────────────┼─────────────────────────────┤
│ Static object reference     │ Metaspace (reference)            │ Heap (object)               │
├─────────────────────────────┼──────────────────────────────────┼─────────────────────────────┤
│ Static method               │ Metaspace (bytecode)             │ N/A                         │
├─────────────────────────────┼──────────────────────────────────┼─────────────────────────────┤
│ Instance variable           │ Heap (with object)               │ Heap (if object)            │
├─────────────────────────────┼──────────────────────────────────┼─────────────────────────────┤
│ Instance method             │ Metaspace (bytecode, shared)     │ N/A                         │
└─────────────────────────────┴──────────────────────────────────┴─────────────────────────────┘

6. How Stack, Heap, and Metaspace Work Together

Let’s see a complete example showing all three memory areas:

public class Application {
    // Static variable
    public static int instanceCount = 0;

    // Instance variable
    private String name;

    // Constructor
    public Application(String name) {
        this.name = name;
        instanceCount++;
    }

    // Static method
    public static void printCount() {
        System.out.println("Count: " + instanceCount);
    }

    // Instance method
    public void display() {
        String message = "Application: " + name;
        System.out.println(message);
    }

    public static void main(String[] args) {
        int localVar = 42;
        Application app1 = new Application("App1");
        Application app2 = new Application("App2");
        app1.display();
        printCount();
    }
}

-------------------------------------------------------------------

STACK (main thread)                METASPACE
┌────────────────────────┐        ┌──────────────────────────────────┐
│ main() frame           │        │ Class: Application               │
│ ┌────────────────────┐ │        │ ┌──────────────────────────────┐ │
│ │ localVar = 42      │ │        │ │ static int instanceCount = 2 │ │
│ │ app1 = 0x100       │─┼────┐   │ │                              │ │
│ │ app2 = 0x200       │─┼──┐ │   │ │ static void printCount() {  │ │
│ └────────────────────┘ │  │ │   │ │   // bytecode               │ │
├────────────────────────┤  │ │   │ │ }                            │ │
│ display() frame        │  │ │   │ │                              │ │
│ ┌────────────────────┐ │  │ │   │ │ void display() {            │ │
│ │ message = 0x300    │─┼─┐│ │   │ │   // bytecode               │ │
│ └────────────────────┘ │ ││ │   │ │ }                            │ │
└────────────────────────┘ ││ │   │ └──────────────────────────────┘ │
                           ││ │   └──────────────────────────────────┘
                           ││ │
HEAP                       ││ │
┌──────────────────────────┼┼─┼────────────────────────┐
│                          ││ │                        │
│ Application @0x100 ◀─────┘│ │                        │
│ ┌────────────────────────┐│ │                        │
│ │ name = 0x400 ──────────┼┼─┼──┐                     │
│ └────────────────────────┘│ │  │                     │
│                            │ │  │                     │
│ Application @0x200 ◀───────┘ │  │                     │
│ ┌────────────────────────┐   │  │                     │
│ │ name = 0x500 ──────────┼───┼──┼──┐                  │
│ └────────────────────────┘   │  │  │                  │
│                              │  │  │                  │
│ String @0x300 ◀──────────────┘  │  │                  │
│ "Application: App1"             │  │                  │
│                                 │  │                  │
│ String @0x400 ◀─────────────────┘  │                  │
│ "App1"                              │                  │
│                                     │                  │
│ String @0x500 ◀─────────────────────┘                  │
│ "App2"                                                 │
└────────────────────────────────────────────────────────┘

7. Real-World Examples with Method Calls

Example 1: Recursive Method and Stack

public class FactorialDemo {

    public static int factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        int result = factorial(5);
        System.out.println(result);
    }
}

-------------------------------------------------------------

STACK (grows downward)
┌─────────────────────┐
│ factorial(5) frame  │  ← Current
│ n = 5               │
├─────────────────────┤
│ factorial(4) frame  │
│ n = 4               │
├─────────────────────┤
│ factorial(3) frame  │
│ n = 3               │
├─────────────────────┤
│ factorial(2) frame  │
│ n = 2               │
├─────────────────────┤
│ factorial(1) frame  │
│ n = 1               │  ← Base case reached, starts unwinding
├─────────────────────┤
│ main() frame        │
│ result = ?          │
└─────────────────────┘

If the recursion goes too deep, you’ll get a StackOverflowError because the stack has a fixed size!

Example 2: Static Variables Across Instances

public class Employee {
    private static int employeeCount = 0;  // Static - in Metaspace
    private String name;                    // Instance - in Heap
    private int id;                         // Instance - in Heap

    public Employee(String name) {
        this.name = name;
        this.id = ++employeeCount;
    }

    public static int getEmployeeCount() {
        return employeeCount;
    }
}

public class Main {
    public static void main(String[] args) {
        Employee emp1 = new Employee("Alice");
        Employee emp2 = new Employee("Bob");
        System.out.println(Employee.getEmployeeCount()); // 2
    }
}

----------------------------------------------------------------------

METASPACE                          HEAP
┌────────────────────────────┐    ┌──────────────────────────┐
│ Class: Employee            │    │ Employee @0x100          │
│ ┌────────────────────────┐ │    │ ┌──────────────────────┐ │
│ │ static int             │ │    │ │ name = 0x300 ──────┐ │ │
│ │ employeeCount = 2      │ │    │ │ id = 1             │ │ │
│ │                        │ │    │ └────────────────────┼─┘ │
│ │ static int             │ │    │                      │   │
│ │ getEmployeeCount() {   │ │    │ Employee @0x200      │   │
│ │   return employeeCount;│ │    │ ┌──────────────────┼───┐ │
│ │ }                      │ │    │ │ name = 0x400 ──┐ │   │ │
│ └────────────────────────┘ │    │ │ id = 2         │ │   │ │
└────────────────────────────┘    │ └────────────────┼─┼───┘ │
                                  │                  │ │     │
                                  │ String @0x300 ◀──┘ │     │
                                  │ "Alice"            │     │
                                  │                    │     │
                                  │ String @0x400 ◀────┘     │
                                  │ "Bob"                    │
                                  └──────────────────────────┘

Notice: Both emp1 and emp2 share the same static variable employeeCount stored in Metaspace!

8. Common Memory Errors and How to Avoid Them

StackOverflowError

Cause: Too many method calls (usually from infinite recursion)

// BAD: Infinite recursion
public void recursiveMethod() {
    recursiveMethod(); // No base case!
}

Solution: Always have a base case in recursion or use iteration instead.

OutOfMemoryError: Java heap space

Cause: Creating too many objects or memory leaks

// BAD: Memory leak
public class LeakyCache {
    private static Map<String, byte[]> cache = new HashMap<>();

    public void addToCache(String key) {
        cache.put(key, new byte[1024 * 1024]); // 1MB
        // Never removed!
    }
}

Solution: Use appropriate data structures, null out references, use WeakHashMap for caches.

OutOfMemoryError: Metaspace

Cause: Too many classes loaded (common in applications with dynamic class loading)

// BAD: Classloader leak
public void loadClasses() {
    while (true) {
        URLClassLoader loader = new URLClassLoader(urls);
        Class<?> clazz = loader.loadClass("com.example.MyClass");
        // Loader never released!
    }
}

Solution:

# Increase Metaspace size
java -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m MyApp

# Monitor Metaspace
java -XX:+PrintGCDetails -XX:+PrintMetaspaceSize MyApp

Here is a comprehensive table overview of the topics we cleared:

╔═══════════════════╦═══════════════════════════════╦═══════════════════════════════╦═══════════════════════════════╗
║     ASPECT        ║            STACK              ║            HEAP               ║          METASPACE            ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ What's Stored     ║ • Primitives                  ║ • All objects                 ║ • Class metadata              ║
║                   ║ • References                  ║ • Instance variables          ║ • Static variables/methods    ║
║                   ║ • Method frames               ║ • Arrays                      ║ • Constant pool               ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Location          ║ JVM memory                    ║ JVM memory                    ║ Native memory                 ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Size              ║ Small                         ║ Large                         ║ Dynamic                       ║
║                   ║ (512KB - 1MB per thread)      ║ (GBs)                         ║ (configurable max)            ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Access Pattern    ║ LIFO                          ║ Random access                 ║ ClassLoader access            ║
║                   ║ (Last In, First Out)          ║                               ║                               ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Speed             ║ Very Fast ⚡⚡⚡              ║ Fast ⚡⚡                     ║ Medium ⚡                    ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Thread Safety     ║ Each thread has its own       ║ Shared by all threads         ║ Shared by all threads         ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Lifetime          ║ Until method returns          ║ Until garbage collected       ║ Until class unloaded          ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Management        ║ Automatic                     ║ Garbage Collector (GC)        ║ Automatic (tunable)           ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Common Errors     ║ StackOverflowError            ║ OutOfMemoryError:             ║ OutOfMemoryError:             ║
║                   ║                               ║ Java heap space               ║ Metaspace                     ║
╠═══════════════════╬═══════════════════════════════╬═══════════════════════════════╬═══════════════════════════════╣
║ Config Flags      ║ -Xss                          ║ -Xms, -Xmx                    ║ -XX:MetaspaceSize             ║
║                   ║                               ║                               ║ -XX:MaxMetaspaceSize          ║
╚═══════════════════╩═══════════════════════════════╩═══════════════════════════════╩═══════════════════════════════╝

Further Reading

About the Author

IT IS A PERSONAL(IF U SEE THIS ITS PROBABLY PUBLIC NOW) JAVA VAULT FOR INTERVIEW PREPARATION


메타데이터
post_id
ce339964733d
slug
understanding-java-memory-a-deep-dive-into-stack-heap-and-metaspace-ce339964733d
url
https://medium.com/@huseynliramazan192/understanding-java-memory-a-deep-dive-into-stack-heap-and-metaspace-ce339964733d
canonical_url
https://medium.com/@huseynliramazan192/understanding-java-memory-a-deep-dive-into-stack-heap-and-metaspace-ce339964733d
author_url
https://medium.com/@huseynliramazan192
status
ok
fetched_at
2026-07-08 08:18:29