Garbage Collection: C, Mark-Sweep & Copying
Learning Goal: To implement both a Mark-and-Sweep and a Semispace Copying Garbage Collector from scratch in C. By building these systems alongside a minimal stack-based virtual machine, you will master low-level memory layout, pointer manipulation, manual allocation limits, dynamic reachability tracing, and strategies to resolve heap fragmentation.
Prerequisites
- C Programming Proficiency: Comfort with structs, unions, raw pointers, dynamic memory allocation (
malloc,free), and bitwise manipulation. - Basic Data Structures: Familiarity with stacks, queues, linked lists, and directed graphs.
- Development Environment: A Unix-like terminal environment with
gccorclangandmake.
Estimated Total Study Time: 26 Hours
Module 1: Pointer Foundations & Manual Memory in C
This module establishes the core hardware-level foundations of system memory. You will study how compiler-managed stack frames contrast with runtime-managed heap allocations. Crucially, this module features a heavy bridging topic on implementing a custom memory allocator, ensuring you understand how to manage, slice, and keep metadata headers on raw memory pools before attempting to automate their cleanup.
Recommended Videos
Why this video
This course is a comprehensive deep-dive into how pointers represent raw physical addresses in hardware. It walks systematically through pointer arithmetic, indirection, the difference between value and reference passes, and provides the baseline fluency required to safely cast and manipulate headers within a manual heap structure.
Why this video
Although presented in C++, the hardware principles detailed here apply directly to C. The video visually demonstrates the physical structure of stack frames (local allocations, LIFO execution, fast CPU pointer increments) versus the global heap. This conceptual model is vital because your future garbage collector will treat stack-allocated variables as the "root set" to scan the heap.
Why this video
This video explains how memory allocators manage the heap under the hood. It discusses how libraries track block sizes and addresses, and how to write a custom tracking wrapper to monitor alloc/free cycles. This serves as an excellent conceptual bridge to managing raw memory.
Why this video
To automate memory, you must first understand how to structure raw chunks of memory manually. This full-length development video guides you in reserving a contiguous raw memory block and splitting it into functional blocks using custom metadata headers. It teaches you how allocators divide raw bytes, manage sizes, and execute internal bookkeeping without relying directly on default glibc malloc/free abstractions.
Knowledge Checkpoint
- Diagram a thread stack frame pointing to a heap-allocated memory block, listing which addresses are managed automatically by the CPU and which must be freed by software.
- Explain the layout of a typical dynamic allocation memory header (e.g., size field, allocation flag) and how a manual allocator navigates raw memory blocks using pointer offsets.
- Demonstrate pointer arithmetic in C by casting a raw byte buffer pointer to a custom metadata structure pointer and extracting its fields.
Module 2: Introduction to Automatic Memory Management
This module shifts the paradigm from manual memory freeing to automated cleanup. You will explore the theoretical boundaries of garbage collection, dissect the runtime relationship between the application (the mutator) and the memory manager (the collector), and contrast reference counting with tracing collectors.
Recommended Videos
Why this video
This summary of Bacon, Attanasio, Lee, Rajan, and Smith’s landmark paper covers the duality of garbage collection. It explains how tracing (identifying live objects to sweep dead ones) and reference counting (tracking incoming edges to immediately free dead objects) are duals of each other, laying a robust conceptual foundation for modern automatic memory management.
Why this video
This video explains the technical challenges of introducing garbage collection to systems languages like C and C++. It discusses how the lack of a managed runtime and the presence of raw pointer casts complicate automatic reachability tracing, highlighting why your custom VM will need strict tracking of root pointers.
Why this video
This video isolates the three core responsibilities of any tracing garbage collector: identifying live objects starting from roots, reclaiming memory from dead objects, and optionally relocating structures. It defines these concepts clearly, helping you plan your upcoming implementation.
Knowledge Checkpoint
- Define the terms mutator and collector and describe how they interact during a program's execution.
- Explain why standard reference counting fails to reclaim cyclic data structures (e.g., node A points to B, B points to A, but both are unreachable from roots), and how tracing collectors solve this.
- Describe the performance and memory footprint trade-offs between reference counting (immediate cleanup, constant overhead) and tracing (deferred batch cleanup, pause times).
Module 3: Mark-and-Sweep: Theory & Design
This module delves into the mechanics of the oldest and most fundamental tracing algorithm: Mark-Sweep. You will study how runtime graphs are traversed, how Dijkstra's Tri-Color Marking abstractly describes collection progress, and how this process results in memory fragmentation.
Recommended Videos
Why this video
This lecture provides an in-depth walkthrough of the Mark-and-Sweep algorithm. It illustrates how the collector traverses the application's object graph starting from stack/global roots, traces live references using depth-first search (DFS) or breadth-first search (BFS), and sweeps unreachable blocks back to the free list.
Why this video
This academic segment offers a precise look at the two distinct phases of Mark-Sweep. It breaks down the state mutations of objects during tracing, detailing the exact logic gate that separates marked (live) entities from unmarked (dead) entities, and showing how the sweep step reclaims dead blocks without moving live ones.
Why this video
Tri-color marking is an essential mental model for tracing collectors. This video explains how objects are classified during a collection cycle: White (unvisited/dead), Gray (visited but children unexamined), and Black (visited alongside all reachable children). This abstraction is crucial for structuring your C code's queue and traversal loops.
Knowledge Checkpoint
- Draw a directed graph of objects and trace a mark phase, coloring nodes White, Gray, or Black as a BFS traversal proceeds.
- Explain how a sweep phase navigates the heap sequentially and reconstructs a "free list" of unallocated memory chunks.
- Define memory fragmentation and explain why a naive Mark-and-Sweep collector exacerbates it when allocating objects of varying sizes over time.
Module 4: Building a Mark-and-Sweep GC in C
With the theory established, you will now write a fully functional Mark-and-Sweep garbage collector integrated into a minimal stack-based virtual machine. Because video tutorials specifically implementing GC in C are rare, this module provides comprehensive technical specifications to guide your implementation alongside the resources.
Technical Scaffolding: Structuring Your VM & GC
Your implementation should feature a VM with a call stack of objects, enabling you to clearly identify the "root set."
#define STACK_MAX 256 #define INITIAL_GC_THRESHOLD 8
typedef enum { OBJ_INT, OBJ_PAIR } ObjectType;
typedef struct sObject { ObjectType type; unsigned char marked; struct sObject* next; // Singly-linked list of all allocated objects
union {
int value;
struct {
struct sObject* head;
struct sObject* tail;
} pair;
};
} Object;
typedef struct { Object* stack[STACK_MAX]; int stackSize; Object* firstObject; // Head of VM-allocated objects list int numObjects; // Track current allocations int maxObjects; // Threshold to trigger GC } VM;
Writing the Collector
- Mark Phase: Iterate through the VM's active stack. For every object found, call a recursive
mark(Object* obj)function that flipsobj->marked = 1. If the object is a pair, recursively mark itsheadandtailchildren. - Sweep Phase: Iterate through the global
firstObjectlinked list. Ifmarkedis0, unchain the object from the list, callfree(), and decrement the allocation counter. Ifmarkedis1, reset it to0for the next cycle.
Recommended Videos
Why this video
Though brief, this video demonstrates the literal loop mechanics of iterating through a tracked list of pointer allocations in C and executing ordered deallocations. It provides a visual guide to the physical clean-up phase of a custom collector.
Why this video
This video analyzes Cello, a library that brings high-level runtime behavior (including garbage collection) to C. It offers practical context on managing pointer structures, custom object types, and memory wrappers, serving as an architectural reference for your custom VM.
Module 4 Guided Lab Instruction
- Initialize the VM: Set
stackSize = 0,firstObject = NULL,numObjects = 0, andmaxObjects = INITIAL_GC_THRESHOLD. - Allocate Safely: Create a function
Object* newObject(VM* vm, ObjectType type). Whenvm->numObjects == vm->maxObjects, trigger agc(vm)collection cycle before allocating. UpdatemaxObjectsdynamic thresholds dynamically based on post-collection occupancy. - Handle Edge Cases: Prevent stack overflows during deep recursive markings of linked data structures by using a flat array as an explicit mark queue (implementing tri-color marking).
Knowledge Checkpoint
- Implement a working
markfunction in C that correctly traverses nested parent-child references without infinite looping on cyclic object graphs. - Implement a working
sweepfunction in C that updates VM linked lists, frees unreferenced memory, and preserves live objects. - Write a test suite simulating heavy VM stack push/pop actions, proving that unreferenced allocations are dynamically reclaimed when thresholds are breached.
Module 5: Copying GC & Cheney's Algorithm Theory
This module addresses memory fragmentation by studying Copying Garbage Collection. You will explore the theory of Cheney’s Semispace Collector, which splits dynamic memory into equal From-Space and To-Space blocks, resolving fragmentation by compacting live objects during collection.
Semispace Heap Layout: +-----------------------------------+-----------------------------------+ | FROM-SPACE | TO-SPACE | | [Obj A] [Obj B] [Free...] | (Empty space reserved for copy) | +-----------------------------------+-----------------------------------+
Recommended Videos
Why this video
This video explains Cheney's non-recursive copying collection algorithm. It uses a "two-finger" pointer system (scan and free) traversing the destination space to execute a breadth-first copying traversal of live objects, eliminating recursion stack overhead.
Why this video
This academic lecture provides a detailed walkthrough of the "Stop and Copy" garbage collection phase. It demonstrates how pointer configurations shift as a program transitions from runtime allocations to a collection freeze, compacting active elements into contiguous memory.
Why this video
This animated clip provides a clear visual demonstration of semispace collectors in action. It illustrates how the "bump-pointer" allocator sequentially places allocations in the active space and how objects are compacted into the target space when a collection is triggered.
Knowledge Checkpoint
- Explain the dual-space layout of a semispace allocator and why it requires twice the memory footprint of an in-place Mark-and-Sweep collector.
- Detail the functions of the
scanandfreepointers in Cheney’s algorithm during a collection phase. - Describe the purpose of "forwarding pointers" and how they prevent copying the same shared object multiple times when processing the object graph.
Module 6: Building a Copying GC in C
In this final module, you will implement a Semispace Copying Garbage Collector in C using Cheney's algorithm. Because video tutorials on writing a Cheney collector in C are virtually non-existent, the technical blueprint below provides the necessary guidance to complete your implementation.
Technical Blueprint: Cheney's Semispace Collector in C
You will allocate a single large block of memory and divide it into two equal semispaces: from_space and to_space.
typedef struct { void* from_space; void* to_space; size_t space_size; void* alloc_ptr; // Current allocation position in from_space void* scan_ptr; // Cheney scanning finger in to_space void* free_ptr; // Cheney copying finger in to_space } SemispaceHeap;
// Forwarding pointer layout inside allocated objects typedef struct sCopyObject { ObjectType type; struct sCopyObject* forwarding; // Points to copy in to_space once migrated union { int value; struct { struct sCopyObject* head; struct sCopyObject* tail; } pair; }; } CopyObject;
The Cheney Tracing Implementation Loop
To trigger collection, initialize scan_ptr and free_ptr at the beginning of to_space.
- Evacuate Roots: Copy all objects directly referenced by the VM stack from
from_spaceintoto_space. For each copied object:- Write its new address to its original
forwardingfield infrom_space. - Advance the
free_ptrinto_space.
- Write its new address to its original
- Scan Phase: Loop while
scan_ptr < free_ptr:- Examine the object at
scan_ptr. - For any pointer field (e.g.,
head,tail) within that object, check if the referenced object has already been copied (i.e., has aforwardingpointer). - If copied, update the field to point to the forwarded address.
- If not copied, copy it to
free_ptr, set itsforwardingaddress, and advancefree_ptr. - Advance
scan_ptrby the current object's size.
- Examine the object at
- Swap Spaces: Once
scan_ptr == free_ptr, swap the roles offrom_spaceandto_spaceby exchanging their pointers. Set the allocation pointer (alloc_ptr) tofree_ptrinside the new active space.
Recommended Videos
Why this video
This video reviews a JVM implementation written in a systems-level language (Rust) featuring a Cheney-based semispace copying collector. The architectural discussion translates directly to C design patterns.
Why this video
This presentation explains the mechanics of region allocators and bump pointer allocation layouts. Understanding how to manage sequential allocation pointers and boundary conditions is essential for constructing a reliable semispace heap.
Module 6 Guided Lab Instruction
- Allocate Memory Pools: Use
malloc()to allocate a single large memory block (e.g., 64KB) and split it into two 32KB pools representingfrom_spaceandto_space. - Build the Bump Allocator: Implement
void* allocate(SemispaceHeap* heap, size_t size). Ifalloc_ptr + sizeexceeds the bounds offrom_space, initiate collection. - Simulate Heap Fragmentation & Compare: Write a test runner that allocates and discards millions of small transient nodes. Compare the memory utilization, execution speed, and cache coherence of your Copying GC against your Mark-and-Sweep GC from Module 4.
Knowledge Checkpoint
- Implement a working
copyhelper function in C that relocates an object, sets its forwarding pointer, and returns its new address. - Implement Cheney's scanning loop using
scan_ptrandfree_ptrto process all reachable child objects. - Write a benchmark showing that a Semispace Copying GC maintains a contiguous free memory space after frequent allocations, avoiding the fragmentation seen in Mark-and-Sweep.
Course Map
Key People Index
- Dr. Jonas Birch (@dr-Jonas-Birch): Systems programmer and educator specializing in low-level allocations. His custom allocator and garbage collector video guides serve as the practical baseline for raw pointer management throughout this curriculum.
- C. J. Cheney: Computer scientist who invented the Cheney non-recursive copying garbage collection algorithm in 1970, introducing the elegant "two-finger" queue implementation.
- Richard Jones: Leading authority on garbage collection and author of The Garbage Collection Handbook, whose theoretical classifications on tracing and concurrent collection inform the modules' structural designs.
- Michael Bernstein: Tech researcher whose presentations on "A Unified Theory of Garbage Collection" clarify the underlying relationship between tracing and reference counting algorithms.
Final Self-Assessment
Complete this comprehensive self-assessment to verify your mastery of garbage collection in C:
- Pointer Safety: I can compile my custom memory allocator code using GCC flags (
-Wall -Wextra -Werror) without experiencing warnings or undefined behaviors. - Stack vs Heap Distinction: I can identify where every variable in my program is allocated and can trace stack references to heap memory.
- Tri-Color Tracking: I can explain tri-color marking and trace how an object graph is traversed and colored during collection.
- Cyclic Graph Resolution: My Mark-and-Sweep GC can identify and reclaim cyclic data structures that have lost their connection to roots.
- Header Structure Configuration: My C structs feature compact metadata headers, using minimum bit allocations to store markers without wasting space.
- No Leak Verification: I have tested my VM and GC under Valgrind, verifying that all allocated memory blocks are successfully freed by the collector upon exit.
- Cheney Pointer Synchronization: My copying collector correctly synchronizes
scan_ptrandfree_ptr, halting precisely when they meet into_space. - Forwarding Pointer Integrity: My Cheney implementation successfully writes forwarding addresses to old objects, preventing duplicate copies of shared references.
- Bump Pointer Allocation: My semispace allocator uses a sequential bump pointer within the active space, eliminating free list traversal times.
- Dynamic GC Tracing: I can trigger collection dynamically when memory limits are reached, run the GC cycle, adjust heap thresholds, and resume normal program execution.
- Benchmarking Evaluation: I have profiled both collectors under heavy load, documenting the execution speed, cache locality, and fragmentation trade-offs of Mark-and-Sweep versus Semispace Copying.








![Garbage Collection Algorithms. Lecture [9/17]: Mark-Sweep GC](https://i.ytimg.com/vi_webp/lXj6j9hVGLQ/maxresdefault.webp)







