How do GOGC and GOMEMLIMIT interact, and how would you tune the GC for a latency-sensitive service?
Go's GC is a concurrent, non-generational, non-moving mark-sweep collector. Pacing:
- GOGC (default 100): the next GC triggers when the heap grows by GOGC% over the live heap after the last GC. Live 200 MB → next GC around 400 MB. Higher GOGC = fewer GCs, more memory.
GOGC=offdisables proportional pacing. - GOMEMLIMIT (Go 1.19+): a soft limit on total Go runtime memory. As usage approaches it, the GC runs more often regardless of GOGC.
A popular pattern for containers: GOGC=off GOMEMLIMIT=~90% of container limit — no GCs until you approach the limit. Risk: if the live heap itself approaches the limit, the GC runs continuously (a "death spiral"); the runtime caps GC CPU at ~50% to mitigate this, but you'll see latency collapse. Safer: keep GOGC=100 (or 200) and set GOMEMLIMIT as a backstop.
import "runtime/debug"
func init() {
debug.SetGCPercent(200)
debug.SetMemoryLimit(900 << 20) // 900 MiB, for a 1 GiB container
}
// Observe: GODEBUG=gctrace=1, runtime/metrics "/gc/cycles/total:gc-cycles",
// "/cpu/classes/gc/total:cpu-seconds"
Tuning GC is secondary; the primary lever is allocating less (alloc_space profile, preallocation, avoiding interface boxing and pointer-heavy structures, which also reduce marking work). Go 1.25 shipped the experimental "Green Tea" GC (GOEXPERIMENT=greenteagc), which improves marking locality for small objects, and Go 1.26 made it the default. Since Go 1.25 the runtime is also container-aware: GOMAXPROCS respects cgroup CPU limits by default.
More on Performance, Profiling & Testing
- Q392Compare the performance of string concatenation approaches: +, fmt.Sprintf, strings.Builder, bytes.Buffer, strings.Join.
- Q393What does this program do?
- Q395Why does preallocating slices and maps matter? What does this print?
- Q396What is false sharing and how would you detect and fix it in Go?
- Q397How does struct field ordering affect memory usage and performance?
- Q398How can you convert between []byte and string without allocating? When is it safe?