Part 2 · 2 chapters · ~12 min

Memory and the Runtime

Stack and heap, escape analysis, the garbage collector (concurrent mark-sweep, GOGC and GOMEMLIMIT), goroutine stacks that grow, the G-M-P scheduler with measured goroutine costs, the netpoller, preemption, and GOMAXPROCS in containers.

5

Goroutines and the scheduler

code
// measured on this machine (go1.23.4, darwin/arm64, GOMAXPROCS 12)
// started 100000 goroutines in 74.3 ms (743 ns each), stack memory ~2.0 KB each
// unbuffered channel round trip: 295 ns
THE G-M-P SCHEDULER
goroutines (G) run on OS threads (M) through logical processors (P)
G: goroutines100,000 of them, ~2 KB stacksP: processorsGOMAXPROCS = 12 hereM: OS threadsrun Pslocal run queuesone per Pglobal run queuenetpollerparks Gs waiting on IO
swipe the figure sideways, or tap expand for full screen
1/5
cheap goroutines
A goroutine starts with a small growable stack (about 2 KB measured here) and costs about 743 ns to start: 100,000 took 74 ms. Compare a Node worker at 14.6 ms or a process at 1.5 ms.
743 ns and ~2 KB each (measured)hundreds of thousands are normal
6

Escape analysis and the GC

code
go build -gcflags=-m ./...        # shows which values escape to the heap and why
// ./ledger.go:12:9: &Account{...} escapes to heap   (returned pointer outlives the function)
// ./ledger.go:20:14: buf does not escape            (stays on the stack: free to allocate)

GODEBUG=gctrace=1 ./ledger       # one line per GC: heap sizes, pause times, CPU share
GOGC=100                         # default: collect when heap grows 100% since last GC
GOMEMLIMIT=900MiB                # soft memory limit (Go 1.19+): GC works harder before hitting it

Go's collector is concurrent and non-moving, tuned for low pause times (sub-millisecond typically) rather than maximum throughput. Fewer heap allocations means less GC work: pass small structs by value, reuse buffers with sync.Pool in hot paths, and check -gcflags=-m before optimising. In containers, set GOMEMLIMIT to about 90% of the memory limit and GOMAXPROCS to the CPU quota (Go 1.25 does the latter automatically; automaxprocs before that).