Mastering Go’s GC: Adjusted Stack, Old Stack, and New Stack Explained
This article explains how Go's garbage collector manages adjusted, old, and new stacks, detailing the stack adjustment process, expansion and contraction steps, and practical techniques—including debugging tools, stack printing, memory profiling, and stress testing—to verify correct behavior.
Adjusted Stack
The adjusted stack is a runtime technique where the GC modifies stack memory during collection to ensure all pointers reference valid objects, especially after stack growth or shrinkage.
Adjustment Process
Mark Phase: GC scans all stack pointers and marks them as visited to prevent accidental reclamation.
Pointer Adjustment: After stack expansion or contraction, GC updates pointers to point to the new stack locations.
Verification: GC validates the adjusted pointers to guarantee they still reference valid objects, avoiding memory errors.
Old Stack and New Stack
When the Go runtime expands or shrinks a stack, the original stack becomes the old stack and the resized one becomes the new stack .
Stack Expansion
When a function needs more stack space, the runtime allocates a larger stack and copies the old stack's contents.
Allocate New Stack: Reserve a larger stack region.
Copy Contents: Transfer all variables and pointers from the old stack.
Update Pointers: Adjust every pointer to reference the corresponding location in the new stack.
Stack Contraction
After a function returns and excess stack space is no longer needed, the runtime may shrink the stack.
Allocate New Stack: Reserve a smaller stack region.
Copy Contents: Move data from the old stack to the new, smaller stack.
Update Pointers: Repoint all pointers to their new locations.
Verification Techniques
To confirm that the GC correctly adjusts stacks, you can use the following methods:
1. Debugging Tools
Use Go debugging tools such as GDB or Delve to set breakpoints and inspect stack pointer changes.
2. Print Stack Information
Add print statements to output the stack’s start address, end address, and pointer locations.
package main
import (
"fmt"
"runtime"
)
func printStack() {
var buf [4096]byte
n := runtime.Stack(buf[:], false)
fmt.Printf("Stack:
%s
", buf[:n])
}
func main() {
printStack()
} go run .\main.go
Stack:
... (stack trace output) ...3. Memory Profiling Tools
Use profiling tools like pprof to generate memory usage reports for the stack.
import (
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Your program logic here
}4. Stress Testing
Run high‑concurrency stress tests to verify the GC’s stability under load.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("Goroutine %d
", i)
printStack()
}(i)
}
wg.Wait()
}Conclusion
Adjusted, old, and new stacks are crucial for Go’s GC to manage stack memory safely. By employing debugging tools, stack printing, profiling, and stress testing, developers can verify the correctness and stability of these mechanisms, leading to more performant and reliable Go applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Ops Development & AI Practice
DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
