skip to content

questions

5

In a heap snapshot, what does an object's retained size measure that its shallow size does not?

level: middleimportance: must knowfreq 66%

answer

  1. two columns, two questions
  2. the object itself versus its subgraph
  3. slots count, targets do not
  4. what disappears if this disappears
  5. shallow sums to the live set

basics

~20 s

Shallow size is the bytes of the object itself: header, fields and its reference slots, but not their targets. Retained size adds everything that would become unreachable if that object did, so it measures what the object keeps alive.

solid answer

~50 s

A heap snapshot gives two size columns per object and they answer different questions. **Shallow size** is the object's own storage — header, scalar fields, and one slot per reference field. The objects those slots point at are separate entries with their own shallow sizes, so shallow sizes across the snapshot add up to the live set. **Retained size** is the sum of the shallow sizes of the object plus every object that would become unreachable if it became unreachable — what it alone keeps alive. That is why sorting by shallow size is nearly useless for a leak: the top rows are big buffers that are usually legitimately in use, and freeing one returns only its own bytes. Sorting by retained size surfaces the small container a few edges above them whose disappearance would take the whole subgraph with it.

go deeper

for a junior

Remember there are two size columns, not one: the bytes of the object itself, and the bytes that would go away with it. Never conclude anything from the largest object alone.

for a middle

Be able to say exactly what each column counts — header, fields and reference slots for one; the exclusively held subgraph for the other — and why shallow sizes sum to the live set while retained sizes do not.

for a senior

Show the reading order on a real snapshot: sort by retained size, skip the graph's spine, find the first surprising entry, then walk up to the reference holding it. Say out loud that retained size is a ceiling on the payoff.

for a principal

Frame it as where diagnosis money goes: the measure you standardise on decides whether engineers chase big buffers for a week or find the one holder in an hour. Note that exclusivity breaks under sharing and that the number says nothing about rate.

## The two numbers an analyzer shows A **heap snapshot** is a dump of the object graph at one instant: every live object, its type, its fields, and the references between them. An analyzer loads that graph and offers two size columns per object. Confusing them is the most common reason a memory investigation stalls. **Shallow size** is the memory of the object itself: - its header (bookkeeping the runtime keeps per object — type identity and collector state); - its scalar fields, laid out with whatever padding alignment requires; - one slot per reference field, sized like a pointer. It does **not** include the objects those slots point at. A long array of references has a large shallow size because the slots are its own storage, but each element is a separate object carrying its own shallow size. Shallow sizes are disjoint by construction, so summing them over the whole snapshot gives the live set. **Retained size** of an object `x` is the sum of the shallow sizes of `x` and of every object that would become unreachable if `x` became unreachable. It answers a different question: *how much memory comes back if this thing goes away?* ## Side by side | | Shallow size | Retained size | |---|---|---| | What it counts | header, scalar fields, reference slots | `x` plus the subgraph only `x` holds | | Depends on graph shape | no — a property of the object's layout | yes — shrinks the moment a second holder appears | | Sums over the snapshot | yes, to the live set | no, entries can nest and double-count | | Answers | how fat is this object | how much would freeing it return | Retained sizes add up only **down the dominator tree**: an object's retained size equals its own shallow size plus the retained sizes of its dominator children, and those children's subtrees are disjoint. A top-twenty list sorted by retained size, though, can easily contain both an object and one of its own ancestors — adding those two counts the same bytes twice. ## Why the biggest object is innocent Open a snapshot from a search-indexing service and sort by shallow size. The first page is large arrays: term buffers, a decompression scratch area, a block of document identifiers. They are big because indexing genuinely needs big contiguous storage, and freeing the largest of them returns exactly its own bytes and nothing else — it is a leaf, holding no one. Now sort the same snapshot by retained size. The top entry is often a container whose shallow size is under a hundred bytes and whose retained size is gigabytes, because every one of those buffers hangs, directly or indirectly, beneath it. That object is the one worth investigating: it is the narrow part of the graph where a single reference decides the fate of everything below. The practical reading order is: 1. Sort by **retained size** and look at the first handful of entries. 2. Ignore entries that are obviously the whole application's spine — the object at the very top of the graph retains nearly everything and tells you nothing. 3. Find the first entry whose retained size is a surprising fraction of the heap for what it claims to be — a lookup table, a queue, a per-request object still present long after the request. 4. Walk **up** from it to see which reference keeps it reachable, and **down** to see what it is holding. ## Where retained size stops helping Retained size is an exclusive measure, and exclusivity is fragile: - If a subgraph is reachable from two independent holders, **neither** holder retains it; the bytes are charged to whatever object both paths pass through. Two suspects can each show a modest retained size while jointly keeping gigabytes alive. - Retained size says what would be freed *if the object became unreachable*. Making it unreachable may take removing more than one incoming reference, so the number is a ceiling on the payoff, not a work estimate. - A reference held with a weaker strength — one the collector is allowed to clear — is treated differently by analyzers, and whether such an edge counts toward retained size is a setting you should check before trusting a figure. - Retained size describes the instant the snapshot was taken. It says nothing about rate; a 1.4 GB retained subgraph that has been 1.4 GB since startup is a design, not a leak. The discipline is simple: shallow size tells you how an object is laid out, retained size tells you what it is responsible for, and only the second one points at a culprit.

  • If shallow sizes sum to the live set, why can retained sizes not be summed the same way?
    Because retained subgraphs nest. An object's retained size already includes the retained sizes of everything beneath it in the dominator tree, so a list sorted by retained size may hold both an ancestor and a descendant, and adding those two counts the same bytes twice. Only dominator siblings are disjoint.
  • An object's retained size is almost equal to its shallow size. What does that tell you?
    That it is effectively a leaf: it holds nothing exclusively, so dropping it returns only its own bytes. Large buffers behave this way, which is exactly why a shallow-size ranking is a poor leak ranking. It can also mean everything it points at is shared with other holders.
  • Why does an object's retained size fall when someone else starts referencing part of its subgraph?
    Retained size counts only what would become unreachable if the object did. A second independent holder means that part of the subgraph now survives the object's disappearance, so those bytes move off its account and onto the account of whatever object both paths pass through.

Shallow size weighs the keyring; retained size weighs everything behind the doors that only those keys open. The keyring is tiny and the rooms are not.

saying these in an interview costs you the question

  • Treats the largest object in the snapshot as the leak
  • Thinks shallow size includes everything the object points at
  • Adds retained sizes of nested entries and gets more than the heap
  • Believes retained size is a property of the object, not of the graph
  • Assumes a big retained size always means a defect rather than a design
open as a page

Why does a heap snapshot's dominator tree tell you which single edge, if cut, would free a whole subgraph?

level: seniorimportance: must knowfreq 54%

basics

~20 s

An object x dominates y when every path from a root to y passes through x. Cutting the reference that makes x reachable therefore makes the whole subtree x dominates unreachable at once, which is what its retained size counts.

open as a page

You have two heap snapshots of a search-indexing service taken an hour apart; how do you diff them to find what is growing?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Compare aggregates, not individual objects: per-type instance counts and shallow bytes, then retained size along the same root paths. The growing type plus a holder count that stayed flat points at one container accumulating entries.

open as a page

For a fleet of search-indexing services, what standing memory-diagnostics policy do you set when a full heap snapshot stalls the process for seconds and writes a file the size of the live set?

level: principalimportance: should knowfreq 33%

basics

~20 s

Keep something cheap always on and make the expensive capture deliberate: low-rate allocation sampling fleet-wide, snapshots taken from a drained instance, one at a time, rate-limited on the fatal path, and the files treated as a data export.

open as a page

Where does a sampled allocation profile mislead you compared with recording every allocation in a service?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Sampling one allocation per fixed interval of bytes estimates bytes per site well, but hides any site whose total allocation is far below one interval and makes object counts noisy. Both forms profile birth, not survival.

open as a page