Golang - Garbage Collection

Go uses a tracing garbage collector based on a concurrent, tri-color mark-and-sweep algorithm.

Designed for low latency, Go’s collector runs concurrently with application goroutines (mutators), keeping Stop-The-World (STW) pauses under a fraction of a millisecond.

The Tri-Color Marking Abstraction

During the marking phase, heap objects are conceptually partitioned into three sets (colors):

  • White: Unvisited objects. At the end of the mark phase, white objects are unreachable garbage and will be swept.
  • Grey: Reachable objects whose references haven't been fully scanned yet.
  • Black: Reachable objects whose references have been completely scanned. Black objects are proven reachable and cannot point directly to white objects without going through a grey object.

Phases of a GC Cycle

A single garbage collection cycle flows through four distinct steps:

[ Running (Off) ] ---> [ STW: Mark Setup ] ---> [ Concurrent Marking ]
                                                              |
[ Concurrent Sweeping ] <--- [ STW: Mark Termination ] <------+

Phase 1: Mark Setup (STW ~Sub-millisecond)

  • Action: Brief Stop-The-World pause.
  • Tasks: Enables the Write Barrier (to catch pointer updates made by user code during concurrent marking) and prepares GC worker background goroutines.

Phase 2: Concurrent Marking (~25% CPU target)

  • Action: Program execution resumes alongside GC mark workers running on g0 stacks across dedicated P processors.
  • Tri-Color Flow:
  1. Root objects (stack pointers, global variables) are colored Grey.
  2. Workers pick a Grey object, scan its pointers, color referenced objects Grey, and promote the original object to Black.
  3. Repeats until the Grey set is empty.

Phase 3: Mark Termination (STW ~Sub-millisecond)

  • Action: Second brief STW pause.
  • Tasks: Disables the write barrier, flushes remaining local work queues, and calculates heap metrics for the next trigger point.

Phase 4: Concurrent Sweeping

  • Action: Program runs fully.
  • Tasks: Unmarked (White) memory spans are lazily reclaimed and returned to the heap allocator (mheap) as goroutines allocate memory.

How Concurrency Is Kept Safe: Write Barriers

Because mutator goroutines run at the same time as the collector, application code could move a reference from a Grey object to a Black object and erase the link from the Grey object.

To prevent the collector from mistakenly sweeping active memory, Go uses a Dijkstra/Yuasa hybrid write barrier.

When a pointer assignment occurs during marking (*ptr = newObj), the write barrier intercept code shaded in runtime assembly ensures newObj is instantly colored Grey before the write finishes.

GC Pacing & Tuning (GOGC & GOMEMLIMIT)

Go triggers a GC cycle based on target allocations rather than fixed timers:

                  Live Heap + (Live Heap * GOGC / 100)
  • GOGC (Default = 100): Defines the growth ratio before GC triggers. At 100, if the live heap after a sweep is 100 MB, the next GC triggers when the heap hits 200 MB.
  • GOMEMLIMIT (Go 1.19+): Sets a hard memory limit for the process. When memory usage approaches GOMEMLIMIT, the runtime automatically adjusts GOGC dynamically to prevent Out-Of-Memory (OOM) kills in containerized environments (like Docker or K8s).