Circular References Cause Memory Leak?

2019-01-03 03:26发布

I'm trying to run down a memory leak in a windows forms application. I'm looking now at a form which contains several embedded forms. What worries me is that the child forms, in their constructor, take a reference to the parent form, and keep it in a private member field. So it seems to me that come garbage-collection time:

Parent has a reference to the child form, via the controls collection (child form is embedded there). Child form is not GC'd.

Child form has a reference to the parent form, via the private member field. Parent form is not GC'd.

Is this an accurate understanding of how the garbage collector will evaluate the situation? Any way to 'prove' it for testing purposes?

6条回答
仙女界的扛把子
2楼-- · 2019-01-03 03:38

As others have already said, GC has no problems with circular references. I'd just like to add, that a common place to leak memory in .NET are event handlers. If one of your forms has an attached event handler to another object which is "alive", then there is a reference to your form and the form will not get GC'd.

查看更多
Animai°情兽
3楼-- · 2019-01-03 03:38

The GC can deal correctly with circular references and if these references were the only things keeping the form alive then they would be collected.
I have had lots of trouble with .net not reclaiming memory from forms. In 1.1 there were some bugs aroung menuitem's (I think) which meant that they didn't get disposed and could leak memory. In this case, adding an explicit call to dispose and clearing the member variable in the form's Dispose method sorted the problem. We found that this also helped reclaim memory for some of the other control types.
I also spent a long time with CLR profiler looking at why forms were not being collected. As far as I could tell, references were being kept by the framework. One per form type. So if you create 100 instances of Form1, then close them all, only 99 would be reclaimed properly. I didn't find any way to cure this.
Our application has since moved to .net 2 and this seems to be much better. Our application memory still increases when we open the first form and doesn't go back down when it is closed but I believe this is becuase of JIT'ed code and extra control libraries that are loaded.
I have also found that although the GC can deal with circular references, it seems to have problems (sometimes) with circular event handler references. IE object1 references object2 and object1 has a method that handles and event from object2. I found circumstances where this didn't release the objects when I expected but I was never able to re-produce it in a test case.

查看更多
Animai°情兽
4楼-- · 2019-01-03 03:42

If both the parent and child are not referenced, but they only reference eachother, they do get GCed.

Get a memory profiler to really check your application and answer all your questions. I can recommend http://memprofiler.com/

查看更多
淡お忘
5楼-- · 2019-01-03 03:48

Great question!

No, Both forms will be (can be) GC'd because the GC does not directly look for references in other references. It only looks for what are called "Root" references ... This includes reference variables on the stack, (Variable is on the stack, actual object is of course on the heap), references variables in CPU registers, and reference variables that are static fields in classes...

All other reference variables are only accessed (and GC'd) if they are referenced in a property of one of the "root" reference objects found by the above process... (or in an object referenced by a reference in a root object, etc...)

So only if one of the forms is referenced somewhere else in a "root" reference - Then both forms will be safe from the GC.

only way I can think of to "prove" it, (without using memory trace utilities) would be to create couple hundred thousand of these forms, in a loop inside a method, then, while in the method, look at the app's memory footprint, then exit from the method, call the GC, and look at the footprint again.

查看更多
相关推荐>>
6楼-- · 2019-01-03 03:48

I'd like to echo Vilx's remark about events, and to recommend a design pattern that helps address it.

Let's say that you have a type that is an event source, e.g.:

interface IEventSource
{
    event EventHandler SomethingHappened;
}

Here is a snippet of a class that handles events from instances of that type. The idea is that whenever you assign a new instance to the property, you first unsubscribe from any previous assignment, then subscribe to the new instance. The null checks ensure correct boundary behaviors, and more to the point, simplify disposal: all you do is null the property.

Which brings up the point of disposal. Any class that subscribes to events should implement the IDisposable interface because events are managed resources. (N.B. I skipped a proper implementation of the Dispose pattern in the example for brevity's sake, but you get the idea.)

class MyClass : IDisposable
{
    IEventSource m_EventSource;
    public IEventSource EventSource
    {
        get { return m_EventSource; }
        set
        {
            if( null != m_EventSource )
            {
                m_EventSource -= HandleSomethingHappened;
            }
            m_EventSource = value;
            if( null != m_EventSource )
            {
                m_EventSource += HandleSomethingHappened;
            }
        }
    }

    public Dispose()
    {
        EventSource = null;
    }

    // ...
}
查看更多
老娘就宠你
7楼-- · 2019-01-03 04:03

Garbage collection works by tracking application roots. Application roots are storage locations that contain references to objects on the managed heap (or to null). In .NET, roots are

  1. References to global objects
  2. References to static objects
  3. References to static fields
  4. References on the stack to local objects
  5. References on the stack to object parameters passed to methods
  6. References to objects waiting to be finalized
  7. References in CPU registers to objects on the managed heap

The list of active roots is maintained by the CLR. The garbage collector works by looking at the objects on the managed heap and seeing which are still accessible by the application, that is, accessible via an application root. Such an object is considered to be rooted.

Now suppose that you have a parent form that contains references to child forms and these child forms contain references to the parent form. Further, suppose that the application no longer contains a references to the parent for or any of the child forms. Then, for the purposes of the garbage collector, these managed objects are no longer rooted and will be garbage collected the next time a garbage collection occurs.

查看更多
登录 后发表回答