Golang - Memory - Stack vs Heap
Introduction
Go manages memory for you, but understanding how it does this is key to writing efficient, high-performance code. Every value in your program is stored in one of two places: the stack or the heap.
The Stack: Fast, Simple, and Temporary
The stack is a simple, highly efficient region of memory used for function calls and their local variables.
Analogy: Think of the stack as a stack of plates.
- When you call a function, you place a new plate on top (a “stack frame”).
- This plate holds all the local variables for that function.
- When the function finishes, you take its plate off the top.
- This is a strict Last-In, First-Out (LIFO) process.
Key Characteristics:
- Extremely Fast: Allocation is trivial—it’s just moving a pointer to the top of the stack. Deallocation is equally fast.
- Self-Cleaning: Memory is automatically reclaimed when a function returns. You don’t need a garbage collector for the stack.
- Fixed Size (per Goroutine): Each goroutine gets its own small stack (initially 2KB). It can grow if needed, but it’s meant for short-lived data.
- Access Pattern: You can only access the top of the stack.
What lives on the stack?
- Local variables of simple types (
int
,bool
, pointers). - Function call information (arguments, return address).
- Structs and arrays, if they are of a known, fixed size at compile time and don’t “escape.”
The Heap: Slower, Flexible, and Long-Lived
The heap is a large, less organized region of memory used for data that needs to live longer than a single function call.
Analogy: Think of the heap as a large, open warehouse.
- When you need to store something, you ask the warehouse manager (the Go runtime) for some space.
- The manager finds a free spot and gives you the address.
- This space can be of any size and can exist for as long as you need it.
- When you’re done, the Garbage Collector is the cleanup crew that comes around to find and throw away things that are no longer being used.
Key Characteristics:
- Slower Allocation: Allocating memory on the heap is more complex. The runtime has to find a suitable free block of memory, which takes more time than just bumping a stack pointer.
- Garbage Collected: The heap is managed by Go’s garbage collector (GC). The GC’s job is to find objects on the heap that are no longer referenced and free that memory.
- Dynamic Size: The heap can grow as your application needs more memory.
- Use Case: For data that needs to be shared between functions or goroutines, or whose size is not known at compile time.
Escape Analysis: The Deciding Factor
So how does Go decide whether to put a variable on the stack or the heap? The Go compiler performs a process called escape analysis.
The compiler analyzes your code to determine if a variable’s lifetime is confined to its function.
- If a variable is only ever used within the function it was created in, it does not escape and can be safely allocated on the stack.
- If the compiler cannot prove that a variable’s use is confined to its function (e.g., you return a pointer to it, or it’s used in a closure that outlives the function), the variable escapes and must be allocated on the heap.
Example:
package main
// This function returns a pointer to its local variable `x`.
// The compiler sees that `&x` will be used after `createUser` returns.
// Therefore, `x` "escapes" to the heap.
func createUser() *User {
x := User{Name: "Alice", Age: 30}
return &x
}
// In this function, the variable `y` is only used locally.
// It does not escape and will be allocated on the stack.
func processUser() {
y := User{Name: "Bob", Age: 25}
// ... do something with y ...
}
type User struct {
Name string
Age int
}
You can see this in action by running the compiler with the -m
flag:
go build -gcflags="-m" ./main.go
The output will show you which variables were moved to the heap.
Summary Table
Feature | Stack | Heap |
---|---|---|
Analogy | A stack of plates | A large warehouse |
Speed | Very Fast | Slower |
Management | Automatic (on function return) | Garbage Collector (GC) |
Data Lifetime | Short-lived (tied to a single function call) | Long-lived (can outlive the function that created it) |
Key Question | “Is this data only needed for this one task?” | “Will this data be needed by someone else, somewhere else, later on?” |
Decided By | Escape Analysis by the compiler | Escape Analysis by the compiler |