Memory & GC
int value = 7;
object box = value;
value = 9;
Console.WriteLine($"value:{value};boxed:{box}");
Console.WriteLine($"same-box:{ReferenceEquals(box, box)}");
Console.WriteLine($"new-box:{ReferenceEquals(box, (object)7)}");
Console.WriteLine("allocated-bytes:not-inferred-from-identity");
Reveal answer and reasoning
The sample starts with value equal to seven and stores a boxed copy in box. Assigning nine to the original value does not change the boxed seven, so value:9;boxed:7 is printed. Comparing box with itself is true. Comparing it with a separately boxed seven is false because those are distinct reference identities in this observable comparison.
The final line explicitly refuses to infer allocated bytes from those identities. The program demonstrates copying and reference identity, not a benchmark or a heap-size census. It does not measure object headers, runtime helper allocations or formatting overhead. If you change the sample to print fewer values or allow the box never to escape, you have changed the observation conditions, which is exactly why a careful allocation experiment needs a defined boundary.
The desktop lab warms a non-inlined array-creation helper, takes a per-thread allocation counter, creates one thousand arrays retained in an outer array, and takes the counter again before formatting output. It reports the logical payload separately from interval allocation. The outer array, array headers and alignment explain why payload length is not the same quantity as allocated bytes. Exact values are runtime observations rather than portable expected output.
The process-wide total is printed afterward and covers a wider scope. Do not subtract it from the per-thread interval and call the difference a precise native-memory estimate. Both exclude native allocation accounting, and their time windows differ. The retained arrays are kept alive deliberately so the experiment has a defined ownership boundary; a different short-lived workload should be measured separately.
What a strong answer covers
- Allocated bytes accumulate even when objects die quickly.
- Per-thread allocation counters cannot span arbitrary async thread switches.
- JIT warmup, measurement allocations and escape behavior can change results.
Common pitfall
Using a per-thread counter across arbitrary awaits, or calling cumulative allocation a live-heap measurement.
Follow-up questions
- What allocations belong outside the timed interval?
- How would a no-retention variant change live bytes without eliminating allocation?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.