Is it possible to have a memory leak in managed co

2019-01-26 06:00发布

问题:

For instance if I have a hierarchical data structure:

class Node
{
    public List<Node> children;
}

and it is populated to many levels down then in one of the parents go:

myNode.children.Clear();

which will clear all the references to the immediate children - but how about all the grand children, grand grand children etc. that were referenced by those immediate children? Is C# clever enough to know they are no longer needed and they will be garbage collected?

I have read using WPF data binding without implementing interface INotifyChanged can cause memory leaks: http://blogs.msdn.com/b/micmcd/archive/2008/03/07/avoiding-a-wpf-memory-leak-with-databinding-black-magic.aspx, how is that possible in a managed environment?

回答1:

Yes, the garbage collector will work out that the grandchildren etc are garbage. Basically, if there's no way of getting to an object, it's considered garbage and eligible for collection.

As for how memory "leaks" are possible in managed code - it's typically if you end up with an object which is reachable via object references, but where there's no way you can end up "clearing" those references via an API.

That's the case in the blog post you quoted:

There is an issue where WPF checks to find things that implement INotifyProperyChanged. If there is a databinding to something not implementing this interface, then it makes a record in a global table. That record doesn't get cleaned up, as WPF has no way of checking when that DB record is no longer needed.

So there's this global table maintaining references, and you have no way of indicating that an item in the table can be cleared.



回答2:

The garbage collector only collects objects that are no longer used - memory leaks are caused by objects still holding references to objects even though they shouldn't.

In your case, if a grand children is used by another object, then .Clear will remove it from the List of Nodes, but the garbage collector will not collect it. It will collect all other grand children though.

Example:

class Foo {
 public Node SomeProperty {get; set;}

    public void SomeFunction(){
        var node = new Node { children = new List<Node>() };
        var childNode = new Node();
        var childNode2 = new Node();
        node.children.Add(childNode);
        node.children.Add(childNode2);
        SomeProperty = childNode2;

        node.children.Clear();
        // childNode will be garbage collected
        // childNode2 is still used by SomeProperty,
        // so it won't be garbage collected until SomeProperty or the instance
        // of Foo is no longer used.
    }
}


回答3:

C# does not care. The it's the CLRs job to do the GC.

The GC starts at known root objects(static fields, local variables,...) and walks the references until it has found all reachable objects. All other objects can be collected(excluding some finalizer related stuff).

So if the child references were really the only references to these objects then the grand children will be collected too. But if some alive outside object still has a reference to one of your nodes this node and all other objects referenced by it will be kept alive.


Managed memory leaks are caused by references which keep objects alive.

For example when using databining the GUI has references to the objects keeping them alive.

Similarly being subscribed to an event keeps the object associated with the event handler alive. So sometimes events use weak references to avoid this problem.



回答4:

As an aside, you can also get memory leaks in .net if you use the unsafe keyword. If you use pointers in the same fashion as c++ etc and aren't careful to ensure you don't "loose" a pointer reference then the GC wouldn't be able to collect it.

example of unsafe block;

unsafe
{
int * ptr1, ptr2;
ptr1 = &var1;
ptr2 = ptr1;
*ptr2 = 20;
}


回答5:

Sure, with C# especially short of the other reference allocation things everyone has talked about, if you have a class that wraps native resources but it's never disposed of (or you lose the reference to it), you can create a leak.

Here is an example from the Image class:

public static void MemLeak()
{
    var src = @"C:\users\devshorts\desktop\bigImage.jpg";

    Image image1 = null;

    foreach (var i in Enumerable.Range(0, 10))
    {
        image1 = Image.FromFile(src);
    }

    image1.Dispose();

    Console.ReadLine();
}

Image is disposable, so since I'm disposing of the image at the end there shouldn't be a leak right? Actually, the fact that you overwrite the reference each time with a new image, means you can't dispose of the underlying GDI+ resources that the old image reference held. This will introduce a memory leak.

Since the gc doesn't call dispose for you and the Image class doesn't override the Finalize method (and call Dispose there), then you've got yourself a leak.



回答6:

Yes, you can have a whole graph of objects (massive data structure) but if none of it is tied down or referred to, it will be garbage collected.

If it's not, either the GC hasn't run (you could try a GC.Collect() for diagnostic purposes but you shouldn't use it in production code) or something is referring to a part of the structure. For example, the UI might be bound to it.



回答7:

Circular references are no problem for the GC in .NET. It uses an algorithm to determine which objects are actually reachable from certain entry points (e.g. the main method).

What can cause meory leaks, however, are objects which are accidentally referenced by static members for example.

Your example falls into the first category and is therefore safe to use.



回答8:

It is possible to have a kind of memory leak in .NET.

If you have an object "A" that registers to an event on another object "B", then "B" gets an reference to "A" and will continue to have so if you do not unregister the event when "A" gets out of scope. In this case "A" cannot be garbage collected as there is still an active reference. It will stick around until "B" is garbage collected.

If you have a situation where "A" objects are created and goes out of scope continually you will get more and more "A"s in memory.



回答9:

I'd suggest reading up on how garbage collection is handled in the .net world -- esentially, it works by following references to find anything that could be referenced by a top level object and frees everything else; it doesn't work with destructors like the C++ world, so you can be happy in the knowledge that managed objects will "just go" if their parent(s) and grand-parent(s) are freed.

Of course, the garbage collector only knows about managed memory, and it is worth looking at the IDisposable pattern if you have any unmanaged resources - this allows deterministic release of non-managed objects.

The complicated bit comes in when dealing with what could reference an object, and it does include some less obvious things, like event handlers, which is where the WPF/INotifyPropertyChanged issue you mentioned comes from.



回答10:

A memory leak is basically a piece of memory that is no longer required for the proper behavior of a program but cannot be freed due to a programming mistake. So the concept of memory leaks has nothing to do with Garbage Collection, C#, or Java.

Take this example:

var list = new List<Node>();
Node a1 = new Node();
Node a2 = new Node();
// ...
Node an = new Node();

// Populate list
list.Add(a1);
list.Add(a2);
// ...
list.Add(an);

// use this list
DoStuffTo(list);

// clear list -- release all elements
list.Clear();

// memory leaks from now on

Note how elements in the list are memory leaks because they are referenced by variables a1 ... an

This is just a simple example of why it is not just up to C# to take care of memory leaks. It is also the responsibility of the developer to fix this:

// Clear references
a1 = null;
a2 = null;
// ...
an = null;

This will tell the C# garbage collector that all these elements should be collected.



回答11:

Yes, leaks in C# are caused when references to objects are not properly removed once those objects are no longer needed. If a reference to an object has been removed, then the object is got rid of by the Garbage Collector when it is run (it does this automatically based at times determined by a carefully tuned algorithm, so best not to manually cause it to run unless you really know what you’re doing!). But if the reference to the object isn’t properly removed, the Garbage Collector still thinks it is needed by the application, so the memory is leaked. It’s particularly common to find this sort of happening with event handlers which aren’t properly got rid of. If an object with children / grandchildren has all references to it removed, then that object as well as all those children / grandchildren will also be removed the next time the Garbage collector is run (unless they're also being referenced from elsewhere).

The best thing is to use a memory profiler, which will let you look at what objects are holding other objects in memory (most let you take snapshots of memory then look at some kind of graph showing the references. If an object still exists when it shouldn’t, you can look at a graph showing what reference is holding that object in memory, and use that to work out where you should have cleared the reference to avoid the memory leaking. There are a few profilers available but I find ants memory profiler by red gate the easiest to use http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/.