Memory Management — Stack, Heap, Virtual Memory

Why Memory Management Matters

Memory is the most critical resource in any computer system. Poor memory management leads to crashes, security vulnerabilities, and terrible performance. Understanding how the OS manages memory helps you write efficient code, debug memory issues, and design scalable systems.

Why this matters for your career:

  • Memory leaks and buffer overflows are among the most common production bugs
  • Memory management knowledge is essential for systems programming (C, C++, Rust)
  • Even high-level languages (Python, JavaScript) benefit from understanding memory
  • Interview questions about stack vs. heap, garbage collection, and virtual memory are very common

Stack vs. Heap

Stack Memory

The stack is a region of memory that grows and shrinks automatically as functions are called and return. It is a LIFO (Last In, First Out) structure.

Stack characteristics:

  • Very fast allocation (just move the stack pointer)
  • Automatic deallocation when function returns
  • Limited size (typically 1-8 MB per thread)
  • Stores local variables, function parameters, return addresses
  • No fragmentation — allocation and deallocation are perfectly nested

Heap Memory

The heap is a region of memory used for dynamic allocation. The programmer (or garbage collector) controls when memory is allocated and freed.

Heap characteristics:

  • Slower allocation (must find a free block)
  • Manual or GC-based deallocation
  • Large size (limited by available RAM)
  • Stores objects, data structures, dynamically sized data
  • Can suffer from fragmentation

Comparison Table

| Aspect | Stack | Heap | |--------|-------|------| | Speed | Fast (pointer increment) | Slow (search for free block) | | Size | Limited (MB) | Large (GB) | | Lifetime | Function scope | Programmer controlled | | Allocation | Automatic | Manual (malloc/new) or GC | | Deallocation | Automatic on return | Manual (free/delete) or GC | | Fragmentation | None | External fragmentation possible | | Thread safety | Each thread has its own stack | Shared across threads | | Data type | Local variables, primitives | Objects, arrays, structs |

Code Example: Stack vs. Heap

import sys

def stack_example():
    # These variables are on the stack
    x = 10
    y = 20
    result = x + y
    return result

def heap_example():
    # Lists are allocated on the heap
    data = [1, 2, 3, 4, 5]
    # The reference 'data' is on the stack, but the list itself is on the heap
    return data

# In Python, integers are objects on the heap too
# But simple integers may be interned (cached)
print(f"Size of int: {sys.getsizeof(42)} bytes")      # 28 bytes
print(f"Size of list: {sys.getsizeof([1,2,3])} bytes")  # 88 bytes + 3*28 for ints

Virtual Memory

Virtual memory is an abstraction layer that gives each process the illusion of having its own private, contiguous address space. The OS maps virtual addresses to physical addresses using page tables.

Benefits of Virtual Memory

| Benefit | Description | |---------|-------------| | Isolation | Each process has its own address space, cannot access other processes' memory | | Security | A process cannot read or write kernel memory | | Simplified programming | Each program sees a contiguous address space starting at 0 | | Efficient RAM usage | Only active pages need to be in RAM; inactive pages can be paged to disk | | Shared memory | Multiple processes can map the same physical page (shared libraries) | | Overcommit | OS can allocate more virtual memory than physical RAM exists |

Paging

Memory is divided into fixed-size blocks called pages (typically 4 KB). The OS maintains a page table that maps virtual pages to physical frames.

Virtual Address → Page Number + Offset
                       ↓
                 Page Table (maps to physical frame)
                       ↓
            Physical Address = Frame Number + Offset

When a process accesses a virtual page that is not in RAM, a page fault occurs. The OS loads the page from disk (swap) into RAM, potentially evicting another page.

Page Replacement Algorithms

| Algorithm | Strategy | Pros | Cons | |-----------|----------|------|------| | FIFO | Evict oldest page | Simple | May evict frequently used pages | | LRU (Least Recently Used) | Evict page unused for longest | Good performance | Hard to implement exactly | | Clock (Second Chance) | Approximate LRU with a circular buffer | Good performance, efficient | Slightly worse than true LRU | | Optimal (Belady's) | Evict page used farthest in future | Best possible | Impossible (needs future knowledge) |

Memory Fragmentation

External Fragmentation

Occurs when free memory is broken into small blocks between allocated blocks. The total free memory may be large, but no single block is large enough to satisfy a request.

Internal Fragmentation

Occurs when allocated memory is slightly larger than requested. For example, allocating 30 bytes from a 32-byte block wastes 2 bytes.

Comparison

| Type | Cause | Solution | |------|-------|----------| | External | Variable-size allocations and deallocations | Memory compaction, or use slab allocator | | Internal | Fixed-size allocation blocks | Use variable-size allocation or buddy system |

Garbage Collection (GC)

Languages like Java, Python, Go, and JavaScript use automatic garbage collection to manage heap memory.

Common GC Algorithms

| Algorithm | How It Works | Pros | Cons | |-----------|-------------|------|------| | Reference Counting | Track number of references to each object | Simple, immediate reclamation | Can't handle circular references | | Mark-and-Sweep | Mark reachable objects, sweep unmarked ones | Handles cycles | Pauses program execution (STW) | | Generational | Objects are young or old; scan young more often | Efficient (most objects die young) | More complex | | Concurrent (Go, Java ZGC) | Run GC concurrently with program | Minimal pauses | CPU overhead |

Python's GC Example

import gc

# Enable debugging
# gc.set_debug(gc.DEBUG_LEAK)

# Create a circular reference (reference counting cannot handle this)
class Node:
    def __init__(self, name):
        self.name = name
        self.ref = None

a = Node("A")
b = Node("B")
a.ref = b
b.ref = a  # circular reference!

del a
del b

# Python's generational GC will collect the cycle
print(f"Unreachable objects collected: {gc.collect()}")

Practical Tips

| Problem | Likely Cause | Fix | |---------|-------------|-----| | Memory leak in long-running process | Objects held by global references | Review static/global containers | | OutOfMemoryError | Heap too small or leak | Increase heap size (-Xmx) or fix leak | | StackOverflowError | Deep recursion | Convert to iterative or increase stack size | | High GC overhead | Too many allocations | Object pooling, reduce allocation rate | | Slow memory allocation | External fragmentation | Use slab allocator or jemalloc |

Summary

Memory management is one of the most important responsibilities of an operating system and programming language runtime. Understanding the stack vs. heap distinction, virtual memory, paging, and garbage collection helps you write efficient, stable, and secure code.

Key takeaways:

  • Stack: fast, limited size, automatic allocation/deallocation, per-thread
  • Heap: flexible, large, manual or GC-managed, shared across threads
  • Virtual memory gives each process its own address space via page tables
  • Page faults occur when accessing data not in RAM — expensive!
  • External fragmentation: many small free blocks; internal fragmentation: wasted bytes within blocks
  • Garbage collection automates heap management but adds overhead and pauses
  • Reference counting can't handle cycles; generational GC is most efficient
  • Monitor memory usage in production to detect leaks early

What's Next: Networking & HTTP

The next chapter covers networking fundamentals — TCP/IP, DNS, HTTP protocols, and how the internet works under the hood.

Memory Hierarchy

Modern computers have multiple levels of memory, each with different speed and size:

| Level | Type | Size | Latency | Managed By | |-------|------|------|---------|-----------| | L1 Cache | CPU cache | 32-64 KB | ~1 ns | Hardware | | L2 Cache | CPU cache | 256-512 KB | ~4 ns | Hardware | | L3 Cache | Shared CPU cache | 2-32 MB | ~12 ns | Hardware | | RAM | Main memory | 8-64 GB | ~80 ns | OS + Hardware | | SSD | Persistent storage | 256 GB - 2 TB | ~100 μs | OS | | HDD | Persistent storage | 1-10 TB | ~10 ms | OS |

The gap between CPU speed and memory speed is the main performance bottleneck in modern computing.

Member Exclusive Free Tutorial

This chapter is free exclusive content for registered members! Please login or register to unlock immediately.

Login / Register Now