Can you have a memory leak in .NET despite garbage collection?
Quick Answer
Yes. The GC only reclaims objects that are unreachable. If something keeps a reference alive forever, that memory is never freed. Classic causes: a static/long-lived collection or cache that only grows, event handlers that aren't unsubscribed, a Timer or captured closure rooting an object, and undisposed types holding unmanaged resources.
Absolutely — garbage collection prevents dangling-pointer bugs, not leaks. The GC frees objects nothing references any more, so a leak in .NET is really “an accidental long-lived reference.” The one I see most is events: a singleton subscribes to something, or something subscribes to a singleton's event and never unsubscribes, so the subscriber can't be collected. Others are caches implemented as a plain dictionary that grows without bound, static collections, and timers that keep their callback target alive. To find them I take heap snapshots over time with dotnet-gcdump and look for object types whose count keeps climbing.
- Event handler leak: a long-lived publisher holds a delegate to a short-lived subscriber → subscriber never collected. Unsubscribe, or use weak events.
- Unbounded caches: use a size/time-bounded cache (
MemoryCachewith limits), not a rawDictionary. - Static fields root everything they reference for the life of the process.
- Diagnose with
dotnet-counters,dotnet-gcdump, or a memory profiler; look for growth in Gen 2 / LOH and rising object counts across snapshots.