Skip to content

Scavenging

Formula

  • without GOMEMLIMIT: (retainExtraPercent+100) / 100 * (heapGoal / lastHeapGoal) * lastHeapInUse
  • with GOMEMLIMIT: (100-reduceExtraPercent) / 100 * memoryLimit
reduceExtraPercent = 10

For services without memlimit, scavenging is lenient—target is larger than heap usage.

With GOMEMLIMIT, scavenging is more aggressive—must respect memory limit.

Another thing to understand: the formula estimates future memory usage based on existing fragmentation.

We don't use heapInUse specific value because OS interaction unit is page—using heapInUse would underestimate and cause excessive RSS return, leading to frequent OS memory requests.

Note: fragmentation here doesn't mean zero-scattered spans within a page, but rather free pages in memory management. Example: free list might have free contiguous ½/3 pages.

Each GC cycle updates the target水位 scavenger must maintain.

Scavenger has two types: asynchronous (like sweep), and active scavenger appearing in alloc section.

If allocation is too fast and about to exceed GOMEMLIMIT, Go runtime tries to return some dirty pages to OS, so new allocation can allocate to a new page, otherwise OOM happens.

Without GOMEMLIMIT, when allocation is too fast, scavenger forces recycling some pages first then allocates. Logic happens in mheap.go's allocSpan function. Example: have several small pages (contiguous ½/3), need to allocate 4 contiguous pages. Scavenger synchronously returns several small ones, then allocates one new large one. Memory allocation emphasizes spatial contiguity.

Emphasize: since GOGC is non-moving, fragmentation is inevitable.

In Go 1.23, runtime uses MADV_DONTNEED to let OS reclaim memory. Unified entry point: func sysUnusedOS(v unsafe.Pointer, n uintptr).

Characteristic: immediately tells OS to reclaim this memory. Due to Go's scavenge mechanism, at this level it maximally ensures enough memory pages are available.

Further Reading