Go: Memory Management and Memory Sweep

ℹ️ This article is based on Go 1.13. The notions about memory management discussed here are explained in my article “Go: Memory Management and Allocation.”
Sweeping the memory is a process that allows Go to know which memory segments are newly available for allocation. However, it does not clean the memory with resetting the bits to zero.
Zeroing the memory
The process of zeroing the memory — moving all bits to zero in the memory segment — is done on the fly during the allocation:

However, we could wonder what strategy Go uses to know which objects are available for allocation. Go actually tracks the free objects thanks to an internal bitmap in each span called allocBits
. Let’s review its workflow, starting with the initial state:

Performance-wise, allocBits
will represent the initial state and will remain the same, but it is helped by freeIndex
, an incremental counter that points to the first free slot.
Then, the first allocation starts:

freeIndex
is now incremented and knows the next free slot based on allocBits
.
Allocations will occur again and, later, the garbage collector will run to free the unused memory. During the marking phase, the garbage collector tracks the memory in-use with a bitmap named gcmarkBits
. Let’s take the same example where the first block is not used anymore by our running program:

Memory in-use is colored in black while the memory not reachable from the current execution will stay in white.
For more information about the marking and coloring phase, I suggest you read my article “Go: How Does the Garbage Collector Mark the Memory?.”
We now have an accurate view of the memory available for allocation with gcmarkBits
. Go can now replace allocBits
with gcmarkBits
, this operation is the sweep of the memory:

However, this must be done on each span and can take a lot of time. Go aims not to block the execution while sweeping the memory and provides two strategies for that.
Sweeping phase
Go provides two ways to sweep the memory:
- with a worker waiting in background that sweeps the spans one by one
- on the fly when the allocation requires a span
Regarding the worker in background, when starting to run your program, Go will set up a background worker — with the only task to sweep the memory — that will sleep and wait for spans to sweep:

We also can see this background worker always coming for sweeping through the cycles in the traces:

The second way to sweep the spans is to do it on the fly. However, since the spans have already been dispatched to the local cache mcache
of each processor, it is hard to track which ones need to be swept first. This is why Go moves all the spans to the mcentral
first:

Then, it will let the local cache mcache
to request them again with sweeping on the fly:

Sweeping on the fly ensures all spans will get swept while saving resources and not blocking the execution.
Collision with Collection Cycles
As seen in the previous section, sweeping can take some time since the only worker is sweeping the spans in the background. However, we could wonder what would happen if another garbage collection cycle starts when the sweeping is not done. In this case, the goroutine that runs the garbage collector will help to finish the remaining sweep before starting the marking phase. Let’s take an example with an allocation of thousands of objects with two successive calls to the garbage collector:

However, this case should not happen if the garbage collector is not forced by the developer. The sweeping that runs in background along with the one done on the fly should be enough since the amount of spans to sweep is proportional to the allocations required to trigger a new cycle.