Memory Model & Garbage Collection¶
Stack vs Heap¶
┌─────────────────────────────────────────┐
│ Stack (per goroutine, starts 2KB) │
│ ├── Local variables (known size) │
│ ├── Function parameters │
│ └── Return addresses │
│ Grows/shrinks automatically │
├─────────────────────────────────────────┤
│ Heap (shared, GC-managed) │
│ ├── Escaped variables │
│ ├── Slice backing arrays (if escaped) │
│ ├── Map internals │
│ └── Interface values (often) │
└─────────────────────────────────────────┘
Stack allocation is free (just move stack pointer). Heap requires GC tracking.
Escape Analysis Rules¶
The compiler decides stack vs heap. A variable escapes to heap when:
| Rule | Example | Escapes? |
|---|---|---|
| Returned pointer | return &x |
Yes |
| Assigned to interface | var i interface{} = x |
Yes |
| Captured by closure outliving scope | go func() { use(x) }() |
Yes |
| Too large for stack | make([]byte, 1<<20) |
Yes |
| Sent to channel | ch <- &x |
Yes |
| Known size, local scope | x := [3]int{1,2,3} |
No |
Check with: go build -gcflags="-m" ./...
GC: Tri-Color Mark & Sweep¶
Phase 1: Mark (concurrent with mutator)
┌───┐ ┌───┐ ┌───┐
│ W │ ──> │ G │ ──> │ B │
│hit│ │rey│ │lac│
│ e │ │ │ │ k │
└───┘ └───┘ └───┘
unreached discovered scanned
White: potentially garbage (not yet seen)
Grey: discovered but children not scanned
Black: fully scanned (definitely alive)
Phase 2: Sweep
All remaining white objects → freed
Write barrier: When a black object gets a reference to a white object, the white object is greyed (prevents premature collection).
GC Tuning¶
| Variable | Default | Effect |
|---|---|---|
GOGC |
100 | GC triggers when heap grows 100% since last GC |
GOMEMLIMIT |
unlimited | Soft memory limit (Go 1.19+) |
GOGC=200 → less frequent GC, more memory, lower CPU
GOGC=50 → more frequent GC, less memory, higher CPU
GOMEMLIMIT=1GiB → GC becomes aggressive near limit
Reducing Allocation Pressure¶
- Pre-allocate slices:
make([]T, 0, expectedCap) - sync.Pool: Reuse temporary objects across GC cycles
- Avoid interface{}: Causes escape + allocation for value types
- strings.Builder: Instead of
s += "..."in loops - Stack-sized arrays:
var buf [64]bytestays on stack - Pointer receivers: Avoid copying large structs
Profile with: go tool pprof -alloc_objects cpu.prof