Go

What do runtime/debug.SetMaxStack, SetMaxThreads, SetGCPercent and SetMemoryLimit do, and when would you use them?

Question 575HardGo 1.22 to 1.25
  • SetMaxStack(bytes): the maximum size of a single goroutine's stack. The default is 1 GB on 64-bit and 250 MB on 32-bit. Going over it is a fatal "goroutine stack exceeds limit" error, which cannot be recovered. Lower it in tests to catch runaway recursion early.
  • SetMaxThreads(n): the maximum number of OS threads. The default is 10000. Going over it crashes the program. Threads pile up because of goroutines blocked in syscalls or cgo, or LockOSThread, not because of ordinary goroutines. Hitting this limit points to a leak of blocking syscalls.
  • SetGCPercent(p): the same setting as GOGC. The next GC starts when the heap grows p% over the live heap. The default is 100, and -1 turns the GC off. The function returns the previous value.
  • SetMemoryLimit(bytes) (Go 1.19): the same setting as GOMEMLIMIT. It is a soft limit on total Go runtime memory (heap, stacks and runtime overhead, but not cgo or mmap'd memory). As memory approaches the limit, GC runs more often. The runtime caps GC CPU at about 50% to avoid a death spiral. The default is math.MaxInt64, meaning no limit.
package main

import (
	"fmt"
	"runtime/debug"
)

func main() {
	fmt.Println(debug.SetGCPercent(50))  // what does this print?
	fmt.Println(debug.SetGCPercent(-1))
	fmt.Println(debug.SetMemoryLimit(-1) == 1<<63-1) // negative = query only
}

Answer (with GOGC unset): 100, then 50, then true. Each setter returns the previous value. SetMemoryLimit with a negative argument only reads the current limit and changes nothing.

Typical use in production: in a container with a 1 GiB limit, set GOMEMLIMIT=900MiB and leave GOGC at 100, or raise it. This avoids OOM kills from GC headroom while keeping GC rare at low load. GOGC=off combined with a memory limit gives maximum throughput for batch jobs.

Gotcha: if the live heap is truly larger than the limit, the process thrashes in GC and then gets OOM-killed anyway. The limit is not a replacement for fixing memory leaks. Prefer environment variables so operators can tune these without a rebuild.

More on Observability, Debugging & Production Operations

All 14 Observability, Debugging & Production Operations questions