Skip to main content

go garbage collector

·2 mins
Disclaimer: This is an unrefined post, see Consistency over Perfectionism.

The Go runtime uses a concurrent, tricolor, mark-sweep garbage collector. Quoting from

At the start of a GC cycle all objects are white. The GC visits all roots, which are objects directly accessible by the application such as globals and things on the stack, and colors these grey. The GC then chooses a grey object, blackens it, and then scans it for pointers to other objects. When this scan finds a pointer to a white object, it turns that object grey. This process repeats until there are no more grey objects. At this point, white objects are known to be unreachable and can be reused.

It’s a non-compacting collector, so it will not move memory blocks to rearrange them.

The pacer schedules garbage collections and determines for how long each of them should run based on the current heap usage.

At the end of each garbage collection, the collector will look at the heap currently in use and create a gap of equivalent size (e.g. if we end up with a 2M heap it will double its size to 4M, creating a 2M gap).

The stress on the heap represents the speed at which the gap fills up.

The pacer aims to keep every Stop The World (SWT) collection to under 100 microseconds. Every STW collection first needs to stop all running goroutines, in order to maintain data integrity on the heap: in order to do so, the collector needs to wait until each goroutine performs a function call, so that it can intercept it. Since the collector needs to run its own goroutines, it will forcibly take 25% of the available threads to perform its work, thus affecting our throughput: if the collector perceives that it cannot complete its job within the planned amount of time, it might acquire more threads in order to speed up the job.

Frequent GCs over a small heap keep the STW time under control: increasing the heap size as an attempt to reduce the GC frequency is likely to backfire and result in longer collection times; the way to be sympathetic with the collector is instead to optimise the code so that it performs less heap allocations.