Progress · 0/13 sections
02 — Memory Allocation, GC & Size Classes
7 min read
02 — Memory Allocation, GC & Size Classes
Go memory kaise allocate karta hai aur Garbage Collector kaise kaam karta hai
Memory Layout — Big Picture
┌───────────────────────────────────────────────────┐
│ Virtual Address Space │
├───────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────┐ │
│ │ HEAP │ │
│ │ (dynamic allocation — GC managed) │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Span 1 │ │ Span 2 │ │ Span 3 │ ... │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────┐ │
│ │ STACKS │ │
│ │ (per-goroutine, growable) │ │
│ └─────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────┐ │
│ │ GLOBALS / BSS │ │
│ │ (package-level variables) │ │
│ └─────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────┐ │
│ │ TEXT (Code) │ │
│ │ (compiled machine code) │ │
│ └─────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────┘
Allocation Hierarchy
Allocation Request
│
▼
┌──────────────┐
│ tiny alloc │ ← < 16 bytes, no pointers
│ (bump ptr) │
└──────┬───────┘
│ miss
▼
┌──────────────┐
│ mcache │ ← Per-P cache (no locking!)
│ (per-P) │ Contains spans for each size class
└──────┬───────┘
│ miss
▼
┌──────────────┐
│ mcentral │ ← Global cache per size class (with locking)
│ (per-size) │
└──────┬───────┘
│ miss
▼
┌──────────────┐
│ mheap │ ← OS se pages request karta hai
│ (global) │ mmap() / VirtualAlloc()
└──────────────┘
Size Classes
Go allocator ~67 size classes use karta hai:
Class Size Objects/Page Waste(%)
1 8 512 0.00
2 16 256 0.00
3 24 170 0.29
4 32 128 0.00
5 48 85 0.29
6 64 64 0.00
7 80 51 0.39
8 96 42 2.08
...
67 32768 1 0.00
// 32KB se bada → directly from heap (large allocation)
Size Class ka kaam:
Request: 20 bytes
↓
Round up to nearest size class: 24 bytes (class 3)
↓
Allocate 24 bytes from span of class 3
↓
Waste: 4 bytes (20/24 = ~17% waste for this request)
↓
Overall waste controlled to max 12.5%
Generated by src/runtime/_mkmalloc/mksizeclasses.go
mallocgc — Core Allocation Function
// src/runtime/malloc.go
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
// 1. Tiny allocation (< 16 bytes, no pointers)
// → bump pointer in tiny allocator
// 2. Small allocation (16 bytes - 32 KB)
// → find appropriate size class
// → allocate from mcache span
// 3. Large allocation (> 32 KB)
// → allocate directly from heap
// → gets its own span
// 4. If GC needed → trigger GC
// 5. Return pointer
}Tiny Allocator (< 16 bytes)
// Very small objects without pointers (like small ints, bools)
// Combine multiple tiny objects into one 16-byte block
// Example:
var a int8 // 1 byte ┐
var b int8 // 1 byte ├─ all in same 16-byte block!
var c int8 // 1 byte ┘Garbage Collector — Tri-color Mark & Sweep
GC Algorithm
┌─────────────────────────────────────────────┐
│ Tri-Color Mark & Sweep │
├─────────────────────────────────────────────┤
│ │
│ WHITE = unmarked (potentially garbage) │
│ GREY = marked, children not scanned yet │
│ BLACK = marked, all children scanned │
│ │
│ 1. Start: all objects WHITE │
│ 2. Mark roots → GREY (stacks, globals) │
│ 3. Pick GREY → scan children → BLACK │
│ 4. Repeat until no GREY left │
│ 5. Sweep: all WHITE objects = garbage! │
│ │
└─────────────────────────────────────────────┘
GC Phases
Program Running
│
▼
┌────────────────────┐
│ 1. Mark Setup │ ← STW (Stop-The-World) — very brief
│ (enable write │ ~10-30 microseconds
│ barrier) │
└────────┬───────────┘
▼
┌────────────────────┐
│ 2. Concurrent │ ← Goroutines AND GC run together!
│ Marking │ 25% CPU for GC by default
│ (scan objects) │
└────────┬───────────┘
▼
┌────────────────────┐
│ 3. Mark │ ← STW — drain remaining work
│ Termination │ very brief
└────────┬───────────┘
▼
┌────────────────────┐
│ 4. Concurrent │ ← Goroutines AND sweeping together
│ Sweeping │ Free unmarked objects
└────────────────────┘
Write Barrier
// GC concurrent hai — goroutines pointers modify kar sakti hain
// Write barrier ensures GC misses nothing
// Pseudo-code:
func writeBarrier(slot *unsafe.Pointer, new unsafe.Pointer) {
// Record the old value (shade it grey)
shade(*slot)
// Write the new value
*slot = new
}
// Ye automatically compiler insert karta hai jab GC active hoGOGC — GC Tuning
import "runtime/debug"
// GOGC = target heap growth percentage before next GC
// Default: 100 (heap doubles before GC triggers)
// GOGC=100 (default):
// Live heap = 100MB → GC triggers at ~200MB
// GOGC=50:
// Live heap = 100MB → GC triggers at ~150MB (more frequent)
// GOGC=200:
// Live heap = 100MB → GC triggers at ~300MB (less frequent)
// GOGC=off:
// GC disabled! (DANGEROUS for long-running programs)
debug.SetGCPercent(50) // programmatically setGOMEMLIMIT — Soft Memory Limit (Go 1.19+)
// Better than GOGC for most production use
debug.SetMemoryLimit(1 << 30) // 1 GiB soft limit
// GC becomes more aggressive as heap approaches limit
// But won't OOM-kill your process (it's a "soft" limit)GOMEMLIMIT=512MiB ./myapp
GOMEMLIMIT=2GiB ./myappGC Trace — Debugging GC
GODEBUG=gctrace=1 ./myapp
# Output example:
# gc 1 @0.004s 1%: 0.012+0.26+0.010 ms clock, 0.049+0.10/0.25/0+0.041 ms cpu, 4->4->0 MB, 5 MB goal, 4 P
#
# Breakdown:
# gc 1 → GC cycle number
# @0.004s → time since program start
# 1% → % of time spent in GC
# 0.012+0.26+0.010 ms clock → STW1 + concurrent + STW2
# 4->4->0 MB → heap before → heap after → live heap
# 5 MB goal → target heap size
# 4 P → number of processors usedStack vs Heap — Escape Analysis Decides
// STACK allocation (fast, automatic cleanup)
func stackAlloc() int {
x := 42 // x stays on stack — no GC needed!
return x
}
// HEAP allocation (slower, needs GC)
func heapAlloc() *int {
x := 42 // x ESCAPES to heap — pointer returned
return &x // GC will clean this up later
}
// Check with:
// go build -gcflags="-m" .Escape Analysis Rules (simplified):
Stack (fast):
✓ Local variables that don't escape function
✓ Small fixed-size allocations
✓ Variables whose lifetime is clearly bounded
Heap (needs GC):
✗ Pointers returned from functions
✗ Variables stored in interfaces
✗ Variables captured by closures that outlive function
✗ Large allocations
✗ Variables whose size is not known at compile time
Memory Statistics
import "runtime"
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MB\n", m.Alloc/1024/1024) // current heap
fmt.Printf("TotalAlloc = %v MB\n", m.TotalAlloc/1024/1024) // cumulative
fmt.Printf("Sys = %v MB\n", m.Sys/1024/1024) // OS se liya
fmt.Printf("NumGC = %v\n", m.NumGC) // GC cycles
fmt.Printf("HeapObjects = %v\n", m.HeapObjects) // live objects
fmt.Printf("GCCPUFrac = %v%%\n", m.GCCPUFraction*100) // GC CPU usageruntime/metrics — Modern Metrics API (Go 1.16+)
import "runtime/metrics"
// Available metrics discover karo
descs := metrics.All()
for _, d := range descs {
fmt.Printf("%s: %s\n", d.Name, d.Description)
}
// Specific metrics read karo
samples := []metrics.Sample{
{Name: "/memory/classes/heap/free:bytes"},
{Name: "/gc/cycles/automatic:gc-cycles"},
{Name: "/sched/goroutines:goroutines"},
}
metrics.Read(samples)
for _, s := range samples {
fmt.Printf("%s = %v\n", s.Name, s.Value)
}Important Metric Categories:
| Category | Example Metric | Purpose |
|---|---|---|
| Memory | /memory/classes/heap/free:bytes | Free heap memory |
| GC | /gc/cycles/automatic:gc-cycles | Auto GC cycle count |
| Scheduler | /sched/goroutines:goroutines | Active goroutines |
| CPU | /cpu/classes/gc/total:cpu-seconds | CPU time in GC |
| Sync | /sync/mutex/wait/total:seconds | Mutex wait time |
Finalizers
import "runtime"
type Resource struct {
handle int
}
func NewResource() *Resource {
r := &Resource{handle: openHandle()}
// GC jab Resource collect karega, pehle ye function chalega
runtime.SetFinalizer(r, func(r *Resource) {
closeHandle(r.handle)
fmt.Println("Resource cleaned up!")
})
return r
}
// WARNING: Finalizers pe depend mat karo!
// - Order guaranteed nahi hai
// - Timing guaranteed nahi hai
// - Performance impact hai
// Prefer: explicit Close() method with defer