Change owners, step through generations, pinning and finalization, or compare memory under load. The model explains concepts; the SDK examples measure real runtime observations.
Open the Memory Lab ↗The memory contract
Fragmentation concerns how usable free space is arranged, not merely how many application items remain. A heap can have free space overall yet lack a sufficiently large suitable region for a particular allocation. Compaction can move eligible live objects to reduce holes, while pinned objects and collector-specific policies affect that ability. These are runtime implementation questions. A program's collection count is an ownership observation; it does not reveal the locations of blocks, the size of free gaps, or whether the operating system has received any memory back.
Trace the actual program
The program allocates three small byte arrays and stores them in a list. Removing the middle entry leaves two owned arrays, with payload lengths 512 and 2,048. The exact output is blocks:2 followed by owned-payload-bytes:2560. The sum excludes array headers, list storage, and every unrelated allocation in the process. No collector is asked to run and no physical addresses are inspected. The removed reference may cease to be an ownership path, but these numbers do not prove when that removed array becomes unreachable or is reclaimed.
How the mechanism works
For CoreCLR, movable regions can be compacted under the collector's policy, while large-object and pinned regions need their own qualified analysis. Another runtime may use a non-compacting collector with different free-space management. A high allocation rate, long-lived survivors, and pinning can interact, so total retained bytes alone rarely explain every pause or failed allocation. Supported heap metrics and traces can provide evidence of fragmentation on a specific target. An application's logical payload counters remain useful, but they answer a narrower question about which data the application currently owns.
Boundaries and misconceptions
Do not draw a fake address map from the order of calls to new and call it the observed runtime heap. Allocations can be aligned, optimized, placed in different regions, and interleaved with runtime work. Removing index one from a List also shifts its element references; it does not move byte-array objects into a newly compacted physical order. A generic fragmentation diagram is a conceptual illustration, not a measurement from this sample. Keep that distinction explicit when discussing the CoreCLR collector or Unity's documented non-compacting behavior.
Practice: change one ownership path
First remove the first entry instead of the middle entry. Predict two list items and a payload total of 3,072. Hint: add the lengths of the remaining arrays, not the original list capacity. Then restore the original removal and replace one remaining payload with a larger bounded array. Predict its logical total independently. Explain why a bigger total does not by itself prove more fragmentation; quantity and arrangement are separate properties that require different observations.
Practice: test a second boundary
Next introduce a second owner for the middle array before removing it from blocks, and print that owner's payload length after removal. Predict that the second owner still reads 1,024. Hint: a list removal affects one reference slot, not every path to the object. Design a target-specific investigation comparing retained objects and fragmentation metrics under a repeated bounded workload. Record which collector and tool supply those metrics, and avoid assigning CoreCLR heap-layout conclusions to SharpRune's browser run merely because both execute the same C# source.
Review the evidence
A careful investigation separates logical ownership, collector reachability, reclaimed managed space, contiguous allocation availability, and process memory. The sample deliberately reports only its own list and payload lengths. Use those deterministic facts to verify the workload before collecting lower-level evidence. An ownership fix may reduce retention without immediately reducing process memory, and that does not automatically mean it failed. Require measurements that correspond to the symptom rather than trying to make every memory-related number move together.
- The original remaining payload lengths are 512 and 2,048.
- An emptied reference slot does not describe a physical free region.
- Fragmentation claims require target-specific collector evidence, not a synthetic map.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
var blocks = new List<byte[]> { new byte[512], new byte[1024], new byte[2048] };
blocks.RemoveAt(1);
Console.WriteLine($"blocks:{blocks.Count}");
Console.WriteLine($"owned-payload-bytes:{blocks.Sum(block => block.Length)}");
GC.KeepAlive(blocks);
Expected output
blocks:2 owned-payload-bytes:2560
The original remaining payload lengths are 512 and 2,048.