skip to content

Why can a post-collection floor drift upward for a fortnight even though the service's live set never grew?

level: seniorimportance: nice to knowfreq 30%

answer

  1. not all collections visit everything
  2. a partial floor still holds garbage
  3. the remainder grows between complete passes
  4. compare only like-scoped events
  5. trend the daily minimum floor

basics

~20 s

Because not every floor is the same measurement. A floor taken after a collection that visited only part of memory still contains garbage the collection never looked at, so a series of such floors can drift while the reachable set is flat. Compare floors of equal scope.

solid answer

~40 s

Collections differ in **scope**: many runtimes usually collect only a young region and visit everything far less often. A floor recorded after a partial collection is `live set + garbage in the regions that collection did not visit`, which is not the live set. Two things then make that number drift on its own: the uncollected remainder accumulates slowly between whole-memory collections, and a runtime allowed to grow its region can postpone the whole-memory collection further and further, so the partial floors keep rising. The discipline is to compare like with like — trend only floors recorded after collections of the same scope, preferably whole-memory ones, and take the two ends of the window from comparable events rather than from whatever happened to be logged.

go deeper

for a junior

Know that collectors do not always examine all of memory at once, so the number after a collection is not always the full picture of what is still reachable.

for a middle

Explain what a partial collection leaves behind and why a floor taken after one overstates the live set by the garbage in the regions it skipped.

for a senior

Show that you filter the series by collection scope before trending it, and that you know the fallback — a daily minimum — when the scope is not exposed by the metrics you have.

for a principal

Decide what your platform must expose for a memory verdict to be defensible at all, since a dashboard that trends incomparable numbers produces confident conclusions in both error directions.

## The floor is only the live set when the collection was complete The reading rule "trend the value after collection" assumes every collection reclaimed everything unreachable. Many runtimes do not work that way most of the time. Under the **weak generational hypothesis** — most objects die young — a collector gets most of its benefit by repeatedly collecting a young region and visiting the whole of memory only occasionally. That is a good design, and it means the routine collection event is a **partial** one. After a partial collection the number reported is: `live set + unreclaimed garbage in every region the collection did not visit` The second term is not noise. It grows between whole-memory collections as objects that survived the young region long enough to be moved elsewhere subsequently die there, unnoticed until something visits that region. So a series of partial floors can rise steadily for days while the reachable set is perfectly flat, and then drop sharply the moment a whole-memory collection happens. ## Three ways a floor series drifts with a flat live set 1. **Scope mixing.** The window contains a mixture of partial and whole-memory events and the series is trended over all of them. Whether the line rises then depends on which kind of event happened to land near each end. 2. **Postponed complete collections.** A runtime allowed to expand the memory region it manages has less reason to run a whole-memory collection; the interval between them stretches, so the uncollected remainder at each partial floor is larger than the last. The floor rises because the measurement is getting staler, not because retention is occurring. 3. **Legitimate structures still settling.** A pool growing toward its steady size, or metadata created the first time a code path runs, raises the live set for real and then stops. This one is genuine growth — just not a defect. | floor recorded after | what it contains | safe to trend against | |---|---|---| | a whole-memory collection | the live set | other whole-memory floors | | a partial collection | live set plus untouched garbage | other partial floors, with care | | a mixture of both | an incoherent series | nothing | ## Making the measurement honest - **Filter by scope before fitting anything.** Keep one kind of event in the series, and say which kind when you report the slope. - **Bracket the window with comparable events.** Take a whole-memory floor near the start and another near the end; two comparable numbers a fortnight apart beat a noisy fit over incomparable ones. - **If whole-memory collections are rare, cause one at each end.** A deliberate complete collection at the two boundaries produces the cleanest possible pair of measurements. It costs a pause, which is why you do it at the boundaries and not continuously. - **Do not assume the sampled gauge is post-collection at all.** A periodically sampled memory metric lands wherever the sampling interval falls, which for a saw-tooth is usually neither the peak nor the floor. Drive the series from collection events, not from a timer. ## Why this matters beyond pedantry This trap produces both error directions. It manufactures leaks — a team spends a week hunting a retainer that does not exist, because their dashboard trended partial floors. And it hides them: a genuine slow climb of a few tens of megabytes a day disappears inside the much larger sawing of the uncollected remainder, so the alert never fires until the limit is close. Runtimes differ in how much of this they expose. Some publish the scope of each collection event plainly, some publish only an aggregate memory gauge, and some let the program request a complete collection while others treat such a request as advisory. Where the scope is not exposed, the pragmatic substitute is the **minimum** floor over a long window — the lowest post-collection value seen in a day is very likely to have followed a complete collection — and trending that daily minimum across days is far more robust than trending every sample. The rule to carry away is short: a floor is evidence only when you know what the collection that produced it actually visited.

  • Your metrics expose a memory gauge but not the scope of each collection. What is the practical substitute?
    Trend the daily minimum of the gauge rather than every sample. Over a full day the lowest value observed almost certainly followed the most complete collection that occurred, so a series of daily minima approximates a series of comparable floors. It is coarser than event-driven data — one point a day — but a fortnight of daily minima still resolves a slope of tens of megabytes a day.
  • Why does a complete collection at each end of the window beat a fit over the whole series?
    Because two measurements of the same thing are comparable by construction, while a fit over a mixed series is dominated by the variance the mixing introduces. The pair answers the question directly — the live set was this, a fortnight later it was that — and the difference divided by the elapsed time is the retention rate, with no assumption about what happened in between.

saying these in an interview costs you the question

  • Believes every collection reclaims all unreachable memory
  • Trends partial and complete collection floors in one series
  • Reads a periodically sampled gauge as if it were a floor
  • Interprets a drifting partial floor as proof of retention
  • Thinks a sharp drop after days of climbing disproves a leak