Memory Leak in C#

50,447

Solution 1

Event Handlers are a very common source of non-obvious memory leaks. If you subscribe to an event on object1 from object2, then do object2.Dispose() and pretend it doesn't exist (and drop out all references from your code), there is an implicit reference in object1's event that will prevent object2 from being garbage collected.

MyType object2 = new MyType();

// ...
object1.SomeEvent += object2.myEventHandler;
// ...

// Should call this
// object1.SomeEvent -= object2.myEventHandler;

object2.Dispose();

This is a common case of a leak - forgetting to easily unsubscribe from events. Of course, if object1 gets collected, object2 will get collected as well, but not until then.

Solution 2

I don't think C++ style memory leaks are possible. The garbage collector should account for those. It is possible to create a static object that aggregates object references even though the objects are never used again. Something like this:

public static class SomethingFactory
{
    private static List<Something> listOfSomethings = new List<Something>();

    public static Something CreateSomething()
    {
        var something = new Something();
        listOfSomethings.Add(something);
        return something;
    }
}

That's an obviously stupid example, but it would be the equivalent of a managed runtime memory leak.

Solution 3

As others have pointed out, as long as there's not an actual bug in the memory manager, classes that don't use unmanaged resources won't leak memory.

What you see in .NET is not memory leaks, but objects that never get disposed. An object won't get disposed as long as the garbage collector can find it on the object graph. So if any living object anywhere has a reference to the object, it won't get disposed.

Event registration is a good way to make this happen. If an object registers for an event, whatever it registered with has a reference to it, and even if you eliminate every other reference to the object, until it unregisters (or the object it registered with becomes unreachable) it will stay alive.

So you have to watch out for objects that register for static events without your knowledge. A nifty feature of the ToolStrip, for instance, is that if you change your display theme, it will automatically redisplay in the new theme. It accomplishes this nifty feature by registering for the static SystemEvents.UserPreferenceChanged event. When you change your Windows theme, the event gets raised, and all of the ToolStrip objects that are listening to the event get notified that there's a new theme.

Okay, so suppose you decide to throw away a ToolStrip on your form:

private void DiscardMyToolstrip()
{
    Controls.Remove("MyToolStrip");
}

You now have a ToolStrip that will never die. Even though it isn't on your form anymore, every time the user changes themes Windows will dutifully tell the otherwise-nonexistent ToolStrip about it. Every time the garbage collector runs, it thinks "I can't throw that object away, the UserPreferenceChanged event is using it."

That's not a memory leak. But it might as well be.

Things like this make a memory profiler invaluable. Run a memory profiler, and you'll say "that's odd, there seem to be ten thousand ToolStrip objects on the heap, even though there's only one on my form. How did this happen?"

Oh, and in case you're wondering why some people think property setters are evil: to get a ToolStrip to unregister from the UserPreferenceChanged event, set its Visible property to false.

Solution 4

Delegates can result in unintuitive memory leaks.

Whenever you create a delegate from an instance method, a reference to that instance is stored "in" that delegate.

Additionally, if you combine multiple delegates into a multicast delegate, you have one big blob of references to numerous objects that are kept from being garbage collected as long as that multicast delegate is being used somewhere.

Solution 5

If you are developing a WinForms application, a subtle "leak" is the Control.AllowDrop property (used to enable Drag and Drop). If AllowDrop is set to "true", the CLR will still hold onto your Control though a System.Windows.Forms.DropTarget. To fix this, make sure your Control's AllowDrop property is set to false when you no longer need it and the CLR will take care of the rest.

Share:
50,447
Joan Venge
Author by

Joan Venge

Professional hitman.

Updated on March 08, 2020

Comments

  • Joan Venge
    Joan Venge about 4 years

    Is it ever possible in a managed system to leak memory when you make sure that all handles, things that implement IDispose are disposed?

    Would there be cases where some variables are left out?

  • Joan Venge
    Joan Venge about 15 years
    But when the list goes out of scope, that should clear all of the items inside the list? Or does the list persist even if there is a reference to one of its elements?
  • Sergey
    Sergey about 15 years
    No one is stopping you from creating tons of objects that your program doesn't use. When they are referenced in some static field, they don't go out of scope automatically, they have to be "disconnected" from the graph of objects explicitly.
  • gbjbaanb
    gbjbaanb about 15 years
    IIS - Internet Information Server - Microsoft's webserver / webserving platform.
  • Curt Hagenlocher
    Curt Hagenlocher about 15 years
    Still true today -- only DynamicMethods get GC'd while the AppDomain is alive.
  • Jon Ediger
    Jon Ediger about 15 years
    This is definitely an issue in directoryservices objects
  • Kevin Laity
    Kevin Laity about 15 years
    In this case I'm assuming that the list is the member of a class and so gets kept around. You're correct, if the list goes out of scope, it should clear out those references.
  • Dimon Buzz
    Dimon Buzz about 15 years
    Yeah, not a stupid example. I have code that is pretty much just that, where FileSystemWatcher objects are created once, then stored in a static collection until the program shuts down. All they do is monitor some files for change.
  • AndersK
    AndersK almost 15 years
    I think this is related to the problem with delegates.
  • Brian Rasmussen
    Brian Rasmussen almost 15 years
    That is not entirely true. A blocking finalizer will keep all remaining finalizable objects from being reclaimed by the garbage collector. I.e. they will be rooted by the internal list of objects waiting to be finalized.
  • tranmq
    tranmq almost 15 years
    Objects with finalizers on the finalizing queue waiting for their finalizers to be called are still being referenced.
  • corymathews
    corymathews over 14 years
    In this example what is is being leaked? is it the List, or the var something or both? I am assuming the list but I am not sure.
  • RCIX
    RCIX over 14 years
    The references are being "leaked" into a list so that those objects are never disposed.
  • Chris Marisic
    Chris Marisic over 14 years
    Do you have any articles that explain this further?
  • Konrad Rudolph
    Konrad Rudolph about 14 years
    @Curt: good to know. Until know I thought even DynamicMethods would persist in the AppDomain.
  • Ed S.
    Ed S. almost 13 years
    The LOH will eventually be defrag'd, but it's not deterministic and it may be a while.
  • Nik
    Nik almost 10 years
    Which library was that please?
  • Dinah
    Dinah almost 10 years
    @Nik: don't recall which library but I haven't worked at that company since ~2008 so I'm sure it's some library which is either no longer around or has since been fixed.
  • Ternary
    Ternary over 9 years
    I wrote a console program to illustrate this leak here: gist.github.com/tbowers/cea8721a24e857bd8e78. Every time you hit the key, another Something is created and you see ~40mb allocated. What I don't get is even if I remove the List<> part of this, I still don't see the memory reclaimed after the each while iteration. Is it just that I'm waiting on the GC to collect?
  • Michael Meadows
    Michael Meadows over 9 years
    @Ternary this is probably because your objects survived a generation 0 collection cycle. Read msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx paying special attention to the sections on "Conditions for a garbage collection" and "Generations"
  • Reed Copsey
    Reed Copsey over 8 years
    @JesonMartajaya Commented there - that's a different (non) issue. Your example is not the same as this.
  • rollsch
    rollsch over 7 years
    You should add an explanation of how and why this causes a memory leak. Otherwise people just have to take it on faith.
  • rollsch
    rollsch over 7 years
    You should give an example.