Start Coding Now

Understanding Java Memory Model: Heap, Stack & GC

Deep dive into Java memory management. Learn about heap, stack, garbage collection, and memory optimization techniques.

JC
JavaCompiler TeamPublished on January 11, 2026

Introduction

Understanding Java's memory model is crucial for writing efficient applications and debugging memory-related issues. In this guide, we'll explore how Java manages memory, the role of heap and stack, and how garbage collection works.

Java Memory Architecture

Java memory is divided into several regions:

1. Heap Memory

The heap is where all objects are stored. It's shared among all threads.

public class HeapExample {
    public static void main(String[] args) {
        // Objects are stored in heap
        String name = new String("JavaCompiler"); // Heap allocation
        int[] numbers = new int[1000]; // Array in heap
        
        // Object reference (name) is in stack, object data in heap
    }
}

Heap Regions:

  • Young Generation: New objects are allocated here
- Eden Space - Survivor Spaces (S0, S1)
  • Old Generation (Tenured): Long-lived objects
  • Metaspace: Class metadata (replaces PermGen in Java 8+)

2. Stack Memory

Each thread has its own stack containing:

  • Method call frames
  • Local variables (primitives and references)
  • Return addresses
public class StackExample {
    public static void main(String[] args) {
        int x = 10; // Stored in stack
        int result = multiply(x, 5); // New stack frame created
        System.out.println(result);
    }
    
    static int multiply(int a, int b) {
        int product = a * b; // All local variables in stack
        return product;
    } // Stack frame popped after return
}

Stack vs Heap

AspectStackHeap
SpeedFastSlower
SizeLimitedLarge
Thread SafetyThread-safeRequires synchronization
Memory ManagementAutomatic (LIFO)Garbage Collected
StoresPrimitives, referencesObjects

Garbage Collection

Java automatically reclaims memory from unused objects through garbage collection.

How GC Works

  • Mark: Identify live objects (reachable from GC roots)
  • Sweep: Remove unreachable objects
  • Compact: Defragment memory (optional)
  • GC Roots

    • Static variables
    • Local variables in active threads
    • Active threads themselves
    • JNI references
    public class GCExample {
        static Object staticRef; // GC root
        
        public static void main(String[] args) {
            Object localRef = new Object(); // Reachable from stack
            
            staticRef = new Object(); // Reachable from static
            
            Object temp = new Object();
            temp = null; // Now eligible for GC
            
            // Suggest GC (not guaranteed)
            System.gc();
        }
    }
    

    Types of Garbage Collectors

  • Serial GC: Single-threaded, for small apps
  • Parallel GC: Multi-threaded, throughput-focused
  • G1 GC: Default since Java 9, balanced
  • ZGC: Low-latency, for large heaps
  • Shenandoah: Concurrent, low pause times
  • Memory Monitoring

    JVM Memory Options

    # Set initial heap size
    java -Xms512m MyApp

    Set maximum heap size

    java -Xmx2g MyApp

    Set stack size

    java -Xss512k MyApp

    Enable GC logging

    java -Xlog:gc* MyApp

    Monitoring Code

    public class MemoryMonitor {
        public static void main(String[] args) {
            Runtime runtime = Runtime.getRuntime();
            
            long maxMemory = runtime.maxMemory();
            long totalMemory = runtime.totalMemory();
            long freeMemory = runtime.freeMemory();
            long usedMemory = totalMemory - freeMemory;
            
            System.out.println("Max Memory: " + (maxMemory / (1024 * 1024)) + " MB");
            System.out.println("Total Memory: " + (totalMemory / (1024 * 1024)) + " MB");
            System.out.println("Free Memory: " + (freeMemory / (1024 * 1024)) + " MB");
            System.out.println("Used Memory: " + (usedMemory / (1024 * 1024)) + " MB");
        }
    }
    

    Common Memory Issues

    1. Memory Leak

    // Memory leak example - don't do this!
    public class MemoryLeak {
        private static List<Object> cache = new ArrayList<>();
        
        public void addToCache(Object obj) {
            cache.add(obj); // Objects never removed, memory grows forever
        }
    }
    

    2. OutOfMemoryError

    // This will cause OutOfMemoryError
    public class OOMExample {
        public static void main(String[] args) {
            List<byte[]> list = new ArrayList<>();
            
            while (true) {
                list.add(new byte[1024 * 1024]); // 1MB each
            }
        }
    }
    

    Best Practices

  • Use appropriate data structures
  • Avoid creating unnecessary objects
  • Close resources properly (try-with-resources)
  • Use weak references for caches
  • Profile your application with tools like VisualVM
  • Summary

    Understanding Java memory management helps you:

    • Write more efficient code
    • Debug memory issues
    • Tune JVM parameters for optimal performance
    Practice these concepts in our Java compiler and explore our tutorials for more in-depth learning.

    Try It Yourself!

    Practice the code examples in our free online Java compiler.