Can you have a memory leak in .NET, and what causes it?

.NET Framework & CLR Memory Advanced 3–5 Yrs5+ Yrs Debugging 1 min read

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> or static Dictionary used as a cache with no eviction. Use MemoryCache with 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 in Dispose.
  • Captured variables — a lambda registered on a long-lived object captures this or 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.