skip to content

Heap and Allocation Profiles

The heap profile samples live objects while the allocs profile samples everything ever allocated, and a live heap that only grows is the Go leak signal - references you forgot, not lost pointers.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

4

In a Go heap profile, what is the difference between inuse_space and alloc_space?

level: middleimportance: must knowfreq 72%

answer

  1. one is a snapshot, one is a total
  2. cumulative never goes down
  3. same file, different view
  4. heap and allocs differ only by default
  5. -sample_index picks the column

basics

~20 s

inuse_space is memory still live at the moment of the snapshot; alloc_space is every byte allocated since the process started, freed or not. One profile file carries both, and go tool pprof -sample_index chooses which you look at.

solid answer

~40 s

A Go memory profile records four values per allocation site: `inuse_space`, `inuse_objects`, `alloc_space` and `alloc_objects`. The in-use pair is a snapshot of what is still reachable; the alloc pair is cumulative over the life of the process and never goes down. You pick the view with `go tool pprof -sample_index=alloc_space` (or the shorthand `-alloc_space`) on the very same file — the `heap` and `allocs` profiles are the same data, differing only in that `heap` defaults to `inuse_space` and `allocs` defaults to `alloc_space`. Use in-use to answer "what is holding memory right now", which is the question behind growth and leaks; use alloc to answer "what is producing garbage", which is the question behind a collector that never rests. The objects columns separate a few large allocations from millions of tiny ones.

code

text · 8 lines
text
# what is still live right now
go tool pprof -sample_index=inuse_space ./svc heap.out

# what has been allocated over the whole run
go tool pprof -sample_index=alloc_space ./svc heap.out

# how many objects, not how many bytes
go tool pprof -sample_index=alloc_objects ./svc heap.out

go deeper

for a junior

Learn the four names and what each measures: bytes or objects, live now or allocated ever. Being able to say which column answers 'what is still holding memory' is enough at this level.

for a middle

Explain that both live in one file and that -sample_index switches between them, and that the heap and allocs profiles differ only in their default view. Be able to pick the right column for a stated symptom.

for a senior

Demonstrate the diagnostic split: growth means the in-use view, collector CPU with flat memory means the cumulative view. Show that you read cumulative down to your own frames rather than blaming the top flat entry.

for a principal

Own the interpretation habit on the team: state what each view is evidence of, so reviews stop arguing about leaks from cumulative numbers, and decide which measurement a performance change is required to move.

## Four numbers, one file A Go memory profile is a set of records, one per sampled call stack, and each record carries four measurements: | sample index | meaning | |---|---| | `inuse_space` | bytes allocated by this stack that are **still live** at the snapshot | | `inuse_objects` | number of **still-live** objects allocated by this stack | | `alloc_space` | bytes this stack has allocated **since the process started**, live or long dead | | `alloc_objects` | number of objects it has allocated since the process started | All four are in every profile you take. Nothing is lost by capturing "the wrong one" — you re-open the same file with a different view. ## heap versus allocs: the same data The runtime exposes two profile names, `heap` and `allocs`. They contain identical records. The only difference is the default sample index the tooling opens them with: a `heap` profile opens on `inuse_space`, an `allocs` profile opens on `alloc_space`. Believing they are two separate sampling runs with different content is a common and confusing mistake. Switching views is a flag: ``` go tool pprof -sample_index=inuse_space ./svc heap.out go tool pprof -sample_index=alloc_space ./svc heap.out ``` `-inuse_space`, `-inuse_objects`, `-alloc_space` and `-alloc_objects` exist as shorthands, and inside an interactive session you can set `sample_index` without restarting the tool. ## Which one answers which question **"My process keeps growing / something is being retained."** That is `inuse_space`. You want the stacks whose allocations are still reachable. A cache without eviction, a slice that only ever appends, a map keyed by request id that nothing deletes from, a goroutine parked forever while holding a buffer — all of those show up here and only here. **"My process is not growing, but the collector is running constantly and CPU is high."** That is `alloc_space`. Collection frequency is driven by how fast you allocate, not by how much you keep. A stage that allocates gigabytes of short-lived objects has a flat in-use profile and an enormous cumulative one. If you look only at `inuse_space` here, you see a small tidy heap and conclude nothing is wrong. **"Is this a few big things or a torrent of small things?"** Compare space against objects. Ten thousand 1 MB buffers and ten billion 1-byte-ish objects can produce similar byte totals with completely different fixes: the first is a buffer-reuse problem, the second is a per-item boxing or per-item allocation problem, and the second also costs the collector far more scanning work. ## Reading a profile in practice Start from cumulative rather than flat when you want to attribute cost to *your* code: the flat top of an allocation profile is frequently a standard-library function that everybody calls, and it tells you nothing about which of your call paths is responsible. Walk the cumulative list down until you reach the first frame you own, and start there. A useful discipline is to look at both views before forming a hypothesis. Two profiles taken minutes apart with the same in-use total but very different cumulative totals tell you the heap is stable and churn is the story. An in-use total that climbs across snapshots while the cumulative rate stays constant tells you the opposite: retention. ## Sampling caveat Every one of these numbers is an estimate scaled up from a sample, not an exact count. Big contributors are reliable; a stack that appears once with a suspiciously round byte count may be a single sampled allocation. Do not build an argument on the difference between two small, adjacent entries. ## Common wrong turns * Concluding "memory leak" from a large `alloc_space`. Cumulative allocation always grows; a long-running healthy service has an enormous one. * Explaining collector CPU with `inuse_space`, then finding nothing and giving up. * Re-running the program to "get the other profile" instead of passing a different `-sample_index`. * Reading only flat values and blaming a standard-library allocation helper rather than the caller that asked for the work.

  • The collector is burning CPU but memory is not growing. Which sample index do you open first, and why?
    `alloc_space`, with `alloc_objects` beside it. Collection frequency tracks allocation volume, not live size, so a flat in-use profile is expected and uninformative here. The cumulative view names the stacks producing the garbage, and the objects column tells you whether it is a few large buffers or a torrent of small ones.
  • When does inuse_objects tell you something inuse_space does not?
    When the byte total looks harmless but the object count is enormous. Millions of tiny live objects are cheap in bytes and expensive everywhere else: more work for the collector to mark and scan, more pointers to chase, worse locality. A high object count with a modest byte count usually points at per-item allocation that could be batched or flattened.
  • A colleague says the allocs profile is a separate, more detailed sampling run than the heap profile. Are they right?
    No. Both names return the same records with the same four measurements from the same sampling; only the default view differs, `inuse_space` for `heap` and `alloc_space` for `allocs`. You never need to re-capture to switch — pass `-sample_index` to the file you already have.

inuse_space is a photograph of your desk right now; alloc_space is the receipt roll of everything you ever put on it, including what you threw away an hour ago.

saying these in an interview costs you the question

  • Calls a large alloc_space a memory leak
  • Uses inuse_space to explain collector CPU
  • Thinks heap and allocs are different sampling runs
  • Re-runs the program instead of changing -sample_index
  • Ignores the objects columns entirely
open as a page

How do you capture a Go heap profile with runtime/pprof, and why call runtime.GC() first?

level: juniorimportance: should knowfreq 58%

basics

~20 s

Open a file and call runtime/pprof.WriteHeapProfile on it, usually right after runtime.GC(), because the in-use numbers come from the most recently completed collection. In tests, go test -memprofile mem.out writes the same profile for you.

open as a page

A Go pipeline stage decoding JSON into map[string]any keeps the collector busy while its live heap stays flat. How do you use the heap and allocs profiles to find and cut the allocation volume?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Open the profile at alloc_space and alloc_objects, not the live view: a flat live heap is what churn looks like. Then cut objects per event - a concrete struct instead of map[string]any, json.RawMessage for unread fields - and re-measure.

open as a page

What does runtime.MemProfileRate control in Go, and what is its default value?

level: middleimportance: nice to knowfreq 34%

basics

~10 s

It sets Go's memory-profile sampling rate: about one allocation is recorded per MemProfileRate bytes allocated, default 512 KB. Totals are scaled estimates. A rate of 1 records everything; 0 turns profiling off.

open as a page