Go Garbage Collection — Find the Bug¶
Bug 1 🟢 — Manual GC in Loop¶
Solution
**Bug**: Forcing GC every iteration adds massive CPU overhead. Trust the runtime. **Fix**: remove `runtime.GC()`.Bug 2 🟢 — GOGC=off in Production¶
Solution
**Bug**: GC disabled. Heap grows forever; eventually OOM. **Fix**: never disable in production.Bug 3 🟡 — No GOMEMLIMIT in Container¶
Solution
**Bug**: GC may let heap grow until OS kills the container. Aggressive growth can OOM-kill before GC catches up. **Fix**: set `GOMEMLIMIT=1900MiB` (~95% of container).Bug 4 🟡 — Sub-Slice Leak¶
Solution
**Bug**: subslice keeps backing array alive. **Fix**: copy out:Bug 5 🟡 — sync.Pool Holds State Beyond GC¶
Solution
**Bug**: Pool may discard entries at GC. `Get` may return a fresh `New()`. **Fix**: don't rely on Pool for state retention.Bug 6 🟡 — sync.Pool Without Reset¶
Solution
**Bug**: previous user's data leaks to next. **Fix**:Bug 7 🔴 — Goroutine Leak Pinning Memory¶
Solution
**Bug**: Goroutine never exits. `req.Body` pinned forever. Memory grows linearly. **Fix**: context cancellation:Bug 8 🔴 — Map Doesn't Shrink¶
cache := map[int]string{}
for i := 0; i < 10_000_000; i++ { cache[i] = "x" }
for i := 0; i < 10_000_000; i++ { delete(cache, i) }
// Memory stays high
Solution
**Bug**: map's bucket array stays at peak size. **Fix**: rebuild the map periodically:Bug 9 🔴 — Excessive Mark Assist From Allocation Burst¶
// 1M goroutines each allocating 10 KB
for i := 0; i < 1_000_000; i++ {
go func() {
_ = make([]byte, 10*1024)
}()
}
Solution
**Bug**: Massive allocation burst triggers heavy mark assist. Goroutines throttled; latency spikes. **Fix**: throttle the burst: Or pre-allocate with sync.Pool.Bug 10 🔴 — debug.FreeOSMemory in Hot Path¶
Solution
**Bug**: `FreeOSMemory` is expensive (scavenges, returns pages). Per-iteration calls cripple performance. **Fix**: call rarely (e.g., once after a known large drop, or never). Trust the runtime.In this topic
Modes