Programs allocate and free memory. The schemes for doing this affect performance, correctness, and developer ergonomics. Different languages choose different points on a wide design spectrum.
This page covers the major approaches.
Memory management bugs:
These cause crashes, security vulnerabilities (most CVEs), and subtle corruption.
The design choice affects both performance and the developer's likelihood of these bugs.
The programmer explicitly allocates and frees.
malloc / free (C); new / delete (C++).
Pros:
Cons:
RAII (Resource Acquisition Is Initialization): destructors free resources.
Smart pointers:
unique_ptr: single owner; auto-free on destructionshared_ptr: reference-countedweak_ptr: non-owning observerModern C++ much safer than C-style.
Conventions like "function takes ownership" vs "function borrows."
Without language enforcement, mistakes happen.
Runtime tracks which memory is reachable; reclaims unreachable.
Each object has count. Increment on new reference; decrement on lost reference. Free when count reaches 0.
Pros:
Cons:
Used in: Python (supplemented with cycle detector), Swift (ARC), Objective-C.
Periodically: from roots, mark all reachable; sweep unreachable.
Simple. Doesn't compact.
Mark, then compact live objects to consolidate free space.
Reduces fragmentation.
Two regions; copy live objects to other; abandon old region.
Fast for young objects (most are dead — little to copy).
Most objects die young. Collect young gen frequently; old gen rarely.
Used by JVM, .NET, V8.
GC work overlaps with program execution. Reduces pause times.
ZGC, Shenandoah, .NET background GC.
A middle ground: language enforces correctness without runtime GC.
Ownership: each value has unique owner. Borrowing: temporary references to owned data. Lifetimes: compile-time validation.
Pros:
Cons:
Rust has demonstrated this works at scale.
Generalization. Each value used exactly once (linear) or at most once (affine).
Research languages, parts of Rust.
Allocate from a region; free entire region at once.
Bump allocator. Allocate fast (just increment pointer); free everything together.
Used for:
Pros:
Cons:
Compiler determines regions automatically.
Used in some research languages; Rust does similar with lifetimes.
Specialized allocators for specific workloads:
Pre-allocate blocks of fixed size. Used for objects of known size.
OS kernels for kernel objects.
Allocate in powers of two. Combine adjacent free blocks.
OS for physical memory.
Increment a pointer; never free until all-at-once.
Arena style.
Linked list of free blocks. Per size class.
General-purpose allocators.
Pre-allocated objects of one type. Allocation just pulls from pool.
Common for connections, threads.
General-purpose allocators (malloc):
For latency-sensitive servers: jemalloc or tcmalloc beats default.
Per-function call frame. LIFO. Auto-managed.
Fast: pointer arithmetic only.
Limited size. Recursion can overflow.
Dynamic. Manually or GC-managed.
Slower than stack. Larger.
Default: prefer stack when possible. Heap when necessary (large, dynamic, shared).
Sometimes you bypass language allocator:
Powerful but easy to misuse.
In manual: forgot to free. In GC: held reference. Effect is same: memory grows.
Common causes:
Detection:
Manual; use modern idioms (RAII, smart pointers). Use sanitizers (ASan, UBSan).
GC. Tune GC for workload. Watch for allocation hotspots.
Reference counting + cycle detection. Allocation is cheap; GC is automatic but has overhead.
GC. Limited control. V8 (Node) handles well.
Ownership. Compile-time guarantees. Use Box, Rc, Arc as needed.
Automatic Reference Counting. Manage cycles with weak/unowned.
Languages either:
Memory-unsafe languages produce ~70% of CVEs in major projects. New systems code is increasingly written in memory-safe languages.
Heap allocation: tens to hundreds of nanoseconds.
Stack: free.
Bump allocator: a few nanoseconds.
For high allocation rates, allocator quality matters.
Allocators may scatter objects in memory. Hurts cache.
Pool / arena allocators give better locality.
Memory free but unusable due to fragmentation.
Compacting GCs handle. Manual / non-compacting may not.
Stop-the-world pauses can affect latency.
Modern GCs (ZGC, Shenandoah) target sub-millisecond pauses.
Forgetting cleanup. Pervasive in long-running systems.
Pointer to freed memory. Subtle bugs.
Writing past array bounds. Security vulnerability.
Freeing same memory twice. Crashes or worse.
Tuning without measuring. Often makes things worse.
Working around GC instead of using it. Often counter-productive.
For application code:
For systems code:
For learning: implement an allocator once. Builds intuition.