Repository navigation
memory(gc): copied minor subtracts from-space high-water from an exact live census #7901
Description
Summary
The new exact live-byte census can undercount after collector modes alternate. A full/non-moving sweep publishes exact aggregate live bytes, excluding dead objects left as holes in partially-live Eden blocks. The next copied minor derives its census by subtracting copying_from_space_in_use_bytes(), which is the sum of block bump offsets and therefore includes those already-excluded dead holes.
That subtracts the same dead space a second time. saturating_sub can hide the arithmetic failure and, when the young high-water exceeds unrelated old live bytes, erase old-generation occupancy from heapUsed and major-GC pacing altogether.
Concrete accounting
After a non-moving sweep, suppose:
- old/long-lived live bytes = 40 MiB
- Eden high-water = 10 MiB
- actual live Eden objects = 1 MiB (9 MiB dead holes in partially-live blocks)
- published exact census = 41 MiB
On the next copied minor, if the 1 MiB survives, current code computes:
41 MiB - 10 MiB from-space high-water + 1 MiB survivors = 32 MiB
The correct result is still 41 MiB. With less than 9 MiB of unrelated live data, saturating subtraction reduces the non-from-space component to zero.
Evidence
copying_from_space_in_use_bytes()sums Eden and active-survivor block offsets; it is a high-water/placement quantity, not live occupancy.- The copied minor captures that high-water and the previously exact live census.
- It subtracts the full high-water, then adds copied/promoted survivors.
- Non-copying collection publishes
sweep.arena_live_bytes, the object-walk census. arena_live_allocated_bytes()explicitly describes the stored value as an exact post-GC census.- Current tests cover copied minors after bump allocation/promotions, but do not create a partially-live from-space high-water via a preceding non-moving/full sweep.
Impact
process.memoryUsage().heapUsedand V8-compatible heap telemetry can underreport live memory.- Major/full-GC pacing can be delayed because it consumes the same undercounted metric.
- The error persists in the next census and can grow across mode transitions until a later whole-heap object census repairs it.
- The use of saturating arithmetic makes the invariant violation silent.
Acceptance criteria
- Track or derive live from-space bytes, distinct from block high-water, when converting an exact aggregate census through a copied minor.
- Preserve exact non-from-space live bytes across copied minors, including prior promotions, long-lived objects, old holes, and partially-live Eden blocks.
- Add a regression sequence: seed an old live cohort; run a non-moving/full sweep leaving one young survivor in a partially dead block; run a copied minor; assert
heapUsed == old_live + copied/promoted_livebyte-for-byte. - Add an internal checked/debug invariant that prevents a high-water subtraction from silently consuming unrelated generations.
- Keep high-water/reserved telemetry separate for fragmentation analysis.
Audit context
Found during the v0.5.1481 re-audit of #7879/#7886. The original high-water-as-live defect is fixed for direct post-collection reads; this issue is a cross-mode derivation error in the replacement census.
Activity
fix(gc): subtract live from-space bytes, not the block high-water, in…