Memory and Garbage Collector in Go: How GC Manages Resources

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Memory and Garbage Collector in Go: How Not to Lose Your Mind Over GC

\n

Have you ever wondered where the memory in your Go application goes? Especially when it starts consuming gigabytes, and you're just parsing JSON? Sounds familiar? Welcome to the world of the garbage collector (GC) in Go — a mysterious mechanism that either saves your project or turns it into a sluggish platform. Today we'll break down how memory works in Go, how the GC decides it's time to clean up, and how to avoid falling into the "STW waiting" trap.

\n

If you've been writing Go for at least a couple of months, you've already encountered this: sudden pauses, increased memory consumption, mysterious gc pause entries in profiles. Let's look under the hood and figure out how to manage memory in Go consciously.

\n\n

How Memory is Organized in Go: Heap and Stack

\n

Before talking about the garbage collector, let's understand where data actually lives in Go. Unlike languages like C, where you are responsible for malloc/free, Go uses automatic memory management. But that doesn't mean everything gets dumped into one heap.

\n

Go has two main memory regions:

\n
    \n
  • Stack — fast, automatically managed memory for local variables. Each goroutine has its own stack (starts at 2 KB and grows dynamically).
  • \n
  • Heap — slower but flexible memory for data that "outlives" a function call.
  • \n
\n

The main question: what goes to the heap, and what stays on the stack? This is handled by escape analysis — a smart mechanism in the Go compiler. It analyzes whether a variable "escapes" the function scope. If it does — it goes to the heap. If not — it stays on the stack, and the GC never even knows about it.

\n

Example:

\n
package main\n\nfunc createPointer() *int {\n    x := 42\n    return &x // x "escapes" — goes to the heap\n}\n\nfunc noEscape() int {\n    x := 42\n    return x // x stays on the stack\n}\n
\n

In the first case, the compiler sees that the pointer to x is returned externally and places the variable on the heap. In the second case, everything is simple and fast. This is memory management in Go at the micro level.

\n\n

Garbage Collector in Go: How It Works

\n

Now about the main character — the garbage collector (GC). Go uses a concurrent, tri-color, non-incremental collector with a "mark-and-sweep" algorithm. Sounds scary? Actually, it's quite logical.

\n

The GC in Go goes through three phases:

\n
    \n
  1. Mark Setup (STW) — a short pause to enable the write barrier. Usually takes 10-30 microseconds.
  2. \n
  3. Marking (Concurrent) — the main work: the GC traverses the object graph, marking live objects. Runs in parallel with your code.
  4. \n
  5. Sweep (Concurrent + STW) — cleaning up dead objects. Partially done in parallel, but final synchronization may cause a short pause.
  6. \n
\n

A key feature of the GC in Go is that it does not perform defragmentation. This means that after collection, memory may become fragmented, but Go uses bitmaps to track free blocks, making memory allocation fast.

\n

An example of GC operation can be seen in the logs:

\n
GODEBUG=gctrace=1 go run main.go\n\ngc 1 @0.013s 4%: 0.031+0.42+0.017 ms clock, 0.25+0.10/0.42/0.0+0.13 ms cpu, 4->4->2 MB, 5 MB goal, 8 P\n
\n

Here: 0.031+0.42+0.017 ms — this is STW + Mark + STW. 4->4->2 MB — heap size before, after mark, and after sweep.

\n\n

Why GC Slows Down: STW and Pauses

\n

The main enemy of performance is STW (Stop The World). Although Go tries to minimize pauses, they still happen. Especially if:

\n
    \n
  • You have many pointers (the GC must traverse all objects)
  • \n
  • The heap is large (more objects — longer marking)
  • \n
  • You create a lot of garbage (high allocation rate)
  • \n

Blogs

Book Recommendations