Hello colleagues! Today I would like to talk about the "lungs" of Golang - its Garbage Collector (GC). While this topic is well-known and frequently discussed, since I've taken on the goal of gradually exploring the most important components of the language, I couldn't skip it.
Introduction
The Garbage Collector (GC) in Go is often called "non-ideal" due to its relative simplicity compared to more complex implementations in other programming languages, but this doesn't mean it's ineffective for real applications.
On the contrary, the language development team made a very deliberate and well-reasoned choice in favor of implementation simplicity and behavior predictability, consciously sacrificing some theoretically possible optimizations to create a more reliable and understandable memory management system.
How Does GC Work in Go?
Golang uses a concurrent, tri-color Mark-and-Sweep algorithm. Let's examine its working principle in more detail. As the name suggests, this algorithm has 2 phases:
- Mark Phase - GC marks all reachable objects using three colors: white, gray, and black:
- The first step of the first phase is initial marking, during which there is a very short stop-the-world pause (STW), to identify all root objects (e.g., global variables, stack variables). These roots are marked gray.
- In the second step, GC concurrently traverses the object graph, marking reachable objects as gray and placing them in a queue for scanning. After scanning all child elements of an object, it becomes black. Objects that are unreachable remain white. This step is performed concurrently with the application code, minimizing disruptions.
- The final step of the phase is mark termination, which is also accompanied by a short STW to ensure marking is complete.
- Sweep Phase - removal of all white objects that remained after marking. This operation is performed concurrently with the application's execution.
Abstractions and Concepts
- White - objects that haven't been visited yet (potential garbage).
- Gray - objects that have been visited, but their child elements haven't been scanned yet.
- Black - objects that have been visited, and all their child elements have been scanned. They are considered alive.
- Write Barriers - these are small code fragments inserted by the Go compiler during pointer write operations. They help GC track changes in the object graph during concurrent marking, ensuring that no live objects are mistakenly marked as white and cleaned up.
Stop The World (STW) Pauses
Modern GC in Go has very short STW pauses - averaging <0.5ms
for most programs. This is achieved through:
- Concurrent marking
- Incremental stack scanning
- Write barrier, allowing the program to continue working during garbage collection
Comparison with Other Languages
Java (JVM GC): JVM offers a wide range of GCs (G1, CMS, Parallel, ZGC, Shenandoah), which are often much more complex and configurable. Some of them also aim for low latency (ZGC, Shenandoah) but usually require more attention to configuration. Go GC is simpler by design and less configurable, with an emphasis on "working out of the box". Java GC can lead to longer STW pauses in certain scenarios, although modern implementations have significantly improved.