In the previous article we explored the Go scheduler — how goroutines get multiplexed onto OS threads, the GMP model, and all the tricks the runtime uses to keep your cores busy. But there’s a fundamental problem we haven’t addressed yet: all those goroutines allocate memory, and somebody has to clean it up. That’s the garbage collector’s job, and that’s what we’re exploring today.
In this article we’ll be looking at the garbage collector as it works in Go 1.26, which introduced the GreenTea GC. If you’re using an earlier version of Go, don’t worry — the overall structure is the same. The main difference is in the mark phase, and we’ll briefly explain how the older approach differs whenever we reach that point. If you want to hear more about GreenTea, Michael Knyszek gave a great talk about it at GopherCon 2025 .
Go’s garbage collector is a non-moving, concurrent, tri-color, mark-and-sweep collector. That’s a lot of adjectives — but what do they all mean? Let’s break them down one by one.
Non-moving means the GC never relocates objects in memory. Once an object is allocated at a particular address, it stays there for its entire lifetime. This is a big deal — it means pointers remain valid, which makes things like unsafe.Pointer and cgo interop much simpler. Some garbage collectors (like Java’s G1 or ZGC) do move objects around to compact memory and reduce fragmentation, but Go takes a different approach: it relies on its size-class-based allocator to minimize fragmentation without needing to move anything.
Concurrent means the GC does most of its work while your program keeps running. It doesn’t stop the world (pause all goroutines so only the GC runs) for the entire collection cycle — only for two very brief pauses. The rest of the time, GC goroutines run alongside your application goroutines, sharing CPU time.
Tri-color refers to the algorithm used during the mark phase. Every object is conceptually colored white, grey, or black — we’ll get into the details shortly.
Mark-and-sweep describes the two main operations: first, mark all the objects that are still reachable (alive); then, sweep through the heap and reclaim anything that wasn’t marked (garbage). Simple in concept, tricky in practice — especially when your program is mutating pointers at the same time the GC is trying to trace them.
Note: This article references concepts from Go’s memory allocator — particularly spans, size classes, and the mcache/mcentral/mheap hierarchy. It’s not strictly required reading, but I’d encourage you to check out The Memory Allocator first.
Now that we know what kind of collector Go uses, let’s see how it actually works. The whole process is organized into a cycle of four phases.
The Four Phases of Garbage Collection
... continue reading