How does garbage collection work in Go?
Garbage Collection
Go uses a concurrent, tri-color mark-and-sweep garbage collector with write barriers.
Garbage Collection Phases
- Mark Setup: Preparation for marking phase.
- Marking: Identifying live objects.
- Mark Termination: Completion of marking phase.
- Sweep: Reclaiming memory from dead objects.
graph LR A[Mark Setup] --> B[Marking] B --> C[Mark Termination] C --> D[Sweep] D --> A
Tri-Color Algorithm
Objects are divided into three sets:
- White: Potentially garbage objects.
- Gray: Objects to be scanned.
- Black: Live objects, all pointers scanned.
The algorithm ensures that no black object points to a white object when marking is complete.
Write Barriers
Write barriers ensure correctness of the tri-color invariant during concurrent marking.
Go implements write barriers primarily through compiler instrumentation and runtime support.
Compiler instrumentation:
- The Go compiler (specifically the SSA backend) inserts write barrier calls at appropriate points in the code.
- This happens during the compilation phase, not at runtime.
Runtime support:
- The actual write barrier logic is implemented in the Go runtime, written in Go and assembly. For performance, the core write barrier is implemented in assembly.
Write barrier function:
- The main write barrier function is
writebarrierptr
, implemented in assembly for each architecture. - There’s also a Go version (
gcWriteBarrier
) for debugging and non-optimized builds.
- The main write barrier function is
Barrier activation:
- Write barriers are only active during the marking phase of garbage collection.
- A global variable
writeBarrier.enabled
controls whether barriers are active.
Barrier logic:
- When active, the write barrier ensures that if a pointer is written to a black (already marked) object, the pointed-to object is marked gray.
- This prevents a black object from pointing to a white (unmarked) object without the collector’s knowledge.
Optimizations:
- Go uses a hybrid write barrier combining Dijkstra and Yuasa approaches.
- The compiler performs static analysis to eliminate unnecessary barriers.
GC Triggers
Go’s GC can be triggered by various events:
- Automatic: Based on heap growth (target is 100% heap growth).
- Forced: By calling
runtime.GC()
. - Pacing: GC runs to meet target heap size and CPU utilization.
GC Tuning
Go provides several environment variables and runtime functions for GC tuning:
GOGC
: Sets the initial garbage collection target percentage.GOMEMLIMIT
: Sets a soft memory limit for the heap.
Example of setting GC percentage programmatically:
import "runtime/debug"
func main() {
// Set GC target to 50% :`X` MB => `1.5 * X` MB
debug.SetGCPercent(50)
// Run your program...
}
Sweeping
After marking, sweeping reclaims memory from dead objects:
- Spans with no live objects are returned to the heap.
- Spans with some live objects have their free lists rebuilt.
- Sweeping is done concurrently and on-demand.