How does Go handel memory allocation
Memory Allocator Overview
The Go memory allocator uses a hierarchical structure:
- Heap: The main memory area where dynamically allocated objects reside.
- Spans: Large blocks of memory (usually 8KB) used to allocate objects.
- Objects: Individual allocated pieces of memory.
graph TD Heap --> Span1[Span 1] Heap --> Span2[Span 2] Heap --> Span3[Span 3] Span1 --> Obj1[Object 1] Span1 --> Obj2[Object 2] Span2 --> Obj3[Object 3] Span2 --> Obj4[Object 4] Span3 --> Obj5[Object 5]
Size Classes
Go uses size classes to group objects of similar sizes. This reduces fragmentation and improves allocation speed.
- There are about 70 size classes, ranging from 8 bytes to 32KB.
- Each size class has its own free list of available objects.
Example of size classes:
var class_to_size = [_NumSizeClasses]uint16{
0,
8,
16,
24,
32,
48,
64,
80,
// ... more sizes ...
32768,
}
TCMalloc Inspiration
Go’s allocator is inspired by TCMalloc (Thread-Caching Malloc), with some key differences:
- Per-P Caches: Instead of per-thread caches, Go uses per-P (processor) caches.
- Goroutine Stacks: Special handling for goroutine stacks, which can grow and shrink.
- GC Integration: Tight integration with the garbage collector.
Allocation Process
For small objects (≤32KB): a. Check the P’s mcache for a free object in the appropriate size class. b. If mcache is empty, refill it from the central cache (mcentral). c. If mcentral is empty, allocate a new span from the heap.
For large objects (>32KB):
- Allocate directly from the heap, rounded up to a multiple of the page size.
Stack Allocation
Go uses stack allocation for objects that don’t escape to the heap:
- Escape analysis determines if an object can be stack-allocated.
- Stack objects have very low allocation/deallocation costs.
- Stacks can grow and shrink as needed.
Example of stack vs heap allocation:
func stackAlloc() int {
x := 5 // x is allocated on the stack
return x
}
func heapAlloc() *int {
x := new(int) // x is allocated on the heap
*x = 5
return x
}
Developers can use the -gcflags "-m"
option when building or running their Go programs to see how escape analysis has determined memory allocation for variables.
For example:
go build -gcflags "-m" main.go
Tiny Allocations
Go has a special optimization for tiny allocations (objects ≤16 bytes):
- Multiple tiny objects can be packed into a single memory block.
- This significantly reduces memory overhead for small objects.
Large Object Allocation
Large objects (>32KB) are handled differently:
- Allocated directly from the heap.
- Use a separate free list for efficient reuse.
- May trigger immediate garbage collection if the heap grows too much.
Memory Profiling
Go provides built-in support for memory profiling:
- Use
runtime/pprof
package orgo test -memprofile
flag. - Analyze with
go tool pprof
for detailed memory usage information.