Understanding Memory in Go
Overview
Go manages memory automatically through its runtime, but understanding stack vs heap allocation helps write more efficient code.
Stack vs Heap
Stack
- Fast allocation/deallocation
- Function-local variables
- Fixed size per goroutine (~2KB initial)
- Automatically cleaned up when function returns
Heap
- Dynamic allocation
- Survives function scope
- Managed by garbage collector
- More expensive than stack
Escape Analysis
Go’s compiler decides where to allocate based on escape analysis:
func stackAlloc() int {
x := 42 // Stays on stack
return x // Value copied
}
func heapAlloc() *int {
x := 42 // Escapes to heap
return &x // Pointer returned
}Check Escape Analysis
go build -gcflags="-m" main.go
# Shows: "moved to heap" for escaped variablesWhat Causes Escape
// 1. Returning pointer to local
func escape1() *int {
n := 1
return &n // Escapes
}
// 2. Storing in interface
func escape2() {
n := 1
fmt.Println(n) // Escapes (interface{} argument)
}
// 3. Closure capturing variable
func escape3() func() int {
n := 1
return func() int { return n } // Escapes
}
// 4. Size too large for stack
func escape4() {
_ = make([]byte, 10<<20) // 10MB, escapes
}Memory Layout
Value Types
type Point struct { X, Y int }
p := Point{1, 2} // 16 bytes inlineReference Types
s := make([]int, 10) // Slice header on stack, data on heap
m := make(map[string]int) // Map structure on heapBest Practices
Reduce Allocations
// Bad: allocates each iteration
for i := 0; i < n; i++ {
data := make([]byte, 1024)
process(data)
}
// Good: reuse allocation
data := make([]byte, 1024)
for i := 0; i < n; i++ {
process(data)
}Preallocate Slices
// Bad: multiple reallocations
var result []int
for _, v := range data {
result = append(result, v)
}
// Good: preallocate
result := make([]int, 0, len(data))
for _, v := range data {
result = append(result, v)
}Use Value Semantics When Possible
// May allocate
func process(p *Point) { }
// Stays on stack
func process(p Point) { }Summary
| Location | Characteristics |
|---|---|
| Stack | Fast, automatic, limited size |
| Heap | Dynamic, GC-managed, survives scope |
| Causes Escape | Example |
|---|---|
| Return pointer | return &x |
| Interface | fmt.Println(x) |
| Closure | func() { use(x) } |
| Large size | make([]byte, 1MB) |