Can you have a memory leak in .NET, and what causes it?
Quick Answer
Yes. The GC only collects unreachable objects, so anything still referenced — directly or indirectly — stays alive. The common causes: static fields / caches that grow forever, event handlers never unsubscribed, captured closures held by long-lived objects, undisposed unmanaged resources, and timers or background tasks that keep a reference to a large object graph.
Absolutely — the garbage collector only frees objects that nothing references, so a “leak” in .NET is really unintended reachability. The usual suspects: a static collection used as a cache that never evicts anything; event handlers you subscribe but never unsubscribe, so the publisher pins the subscriber; a lambda on a long-lived object that captures a big graph; and undisposed unmanaged resources. To track one down I watch the memory climb, take two heap snapshots with a profiler, diff them, and see what's holding references to the type that keeps growing.
- Static references — a
static List<T>orstatic Dictionaryused as a cache with no eviction. UseMemoryCachewith size limits / expiration. - Events —
publisher.SomeEvent += handler;makes the publisher hold the subscriber. If the publisher outlives the subscriber and you never-=, the subscriber can't be collected. Weak event patterns or explicit unsubscribe inDispose. - Captured variables — a lambda registered on a long-lived object captures
thisor a big object. - Unmanaged — file handles, GDI objects, native buffers not disposed — leak until process exit.
- Find it: watch memory over time, take two snapshots with a profiler (dotMemory, Visual Studio,
dotnet-gcdump), diff, and look at what's rooting the growing type.