๐ WAYPOINT LESSON
Managed memory leaks
โญโญโญ advancedโณ 15 min read๐ Lesson 215 of 265
How a GC'd language still leaks: roots, events, caches, timers โ and how to find each.
Managed memory leaks
A tracing GC frees what is unreachable. "Leak" in managed code therefore means unreachable-when-you-thought: something reachable holds a root to something you considered dead. The catalog:
- Event subscriptions.
publisher.Event += handlerstores the subscriber in the publisher's invocation list. Long-lived publisher + short-lived subscriber = every subscriber leaks. Fix: IDisposable unsubscribe, weak-event patterns, orIObserverwith explicit disposal. - Static caches without eviction.
static Dictionary<string, T>grows forever; every entry is rooted by the type. Fix:MemoryCachewith size/time limits,ConditionalWeakTablefor metadata, or bounded LRU. - Timers. A live
System.Threading.Timerroots its callback target. Fire-and-forget timers in components that forget to dispose them keep whole object graphs alive. (The callback's TARGET, and everything it references.) - Closures and lambdas. A lambda capturing
thisregistered into a long-lived service roots the whole object. Task runners holding closures beyond their useful life do the same. - Thread-statics and captured contexts (SynchronizationContext, ExecutionContext held by parked threads).
Diagnosis workflow without fancy tooling (the sandbox has no dotnet-dump):
count instances over time (a loop that allocates a probe object each
iteration and a WeakReference you can poll), force collections, and watch
GC.GetTotalMemory climb โ climbing memory across forced full collections
is the leak signature. The checkpoint's weak-reference test IS this probe,
miniaturized.
โก Now practice
Ready to CodeGC drillsAllocation profiling, finalizer costs, event-leak diagnosis, and pooling decisions.
2 challenges ยท ยท ~45 min