none
Simple Console program with IDisposable, memory is growith but never released - we have a leak? RRS feed

  • Question

  • Hi all,

    I have got a simple ConsoleProgram which is creating a list of 9000 IDisposable objects. One such object hold two System.Threading.ManualResetEvent which are closed in the Dispose() method. Please consider the code:

    private static void Main(string[] args) { Console.ReadLine(); Test(9000); Console.WriteLine("end."); Console.ReadLine(); } private static void Test(int c) { Console.WriteLine("Test start."); List<TestObject> list = new List<TestObject>(); for (int i = 0; i < c; i++) { list.Add(new TestObject()); } Console.WriteLine("End of adding. Added: {0} items.", c); Console.ReadLine();

    foreach (TestObject obj in list) { obj.Dispose(); } list = null; Console.WriteLine("Dispose end."); Console.ReadLine(); GC.Collect(0, GCCollectionMode.Forced); GC.Collect(1, GCCollectionMode.Forced); GC.Collect(2, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); Console.WriteLine("After GC collect"); } public class TestObject : IDisposable { public ManualResetEvent mr1 = new ManualResetEvent(true); public ManualResetEvent mr2 = new ManualResetEvent(false); public void Dispose() { mr1.Dispose(); mr2.Dispose(); } }

    I have tested my program in case of consumed memory and memory leaks. I supposed that consumed memory will increase after creating all list objects, but it will decrease after calling the Dispose() method, setting null value to the list object and forcing the clean-up by GC.Collect(). Unfortunately I have observer different behaviour. Please consider following result of my test:

    1. Program starts (nothing created). WorkingSet = 6.700K
    2. List of 9000 objects was created. WorkingSet = 8.716K (memory grow: 2016K)
    3. Program called the Dispose() method an set null to list object. WorkingSet = 8.724K (memory grow form last point: 8K)
    4. Forcing GC clean. WorkingSet = 8.916K (memory grow from last point: 192K)
    5. Program is hanging for more than 20 minutes. WorkingSet = 9.021K (memory grow from last point: 105K, memory grow from the beginning: 2321K)
    6. Program was closed.

    I am really confused about the point 3, 4 and 5. Why the memory was not released? I think that this is a memory leak, because the total memory grow was equal to 2321K and it was never released (even after forcing the clean-up). I know that garbage collector is unpredictable and we never know when he will free the memory, but I thought that calling the GC.Collect() cause that my program will release it, and unfortunately it has consumed more than 2MB and he did not give it back.

    Thank you very much for any answer.



    • Edited by klcikrras Wednesday, August 8, 2012 11:54 AM
    Wednesday, August 8, 2012 11:49 AM

Answers

  • First, put your app in it's own app pool ... this will isolate it from anything else on the box.

    Start looking into the performance counters for .NET. Check out how much is in each generation and the large object heap. If Gen2 is large, then you have long-lived objects, which is usually (especially for a web app) not a good thing. You will also want to look at finalization survivors ... these will be objects that are not properly disposed either by not calling Dispose() or by not included GC.SuppressFinalize() in their Dispose() method.

    Certain aspects of your architecture may also be causing this. How heavily are you using caching? Are you using Sessions? Do you have static, application-wide variables?

    It does sound to me like there is a memory leak. One thing that you can do ... and this is not a fix but a way to test ... is to have a page that calls GC.Collect() a couple of times (it actually takes 2 rounds for the objects that need finalization) and see if that drops the memory. If so, then you can be sure that there is a leak somewhere. If not ... then start looking at what you are holding on to at the application/session level.


    DevBiker (aka J Sawyer)
    Microsoft MVP - Sql Server (StreamInsight)


    Ruminations of J.net


    If I answered your question, please mark as answer.
    If my post was helpful, please mark as helpful.

    Friday, August 10, 2012 12:36 PM
  • Hi Kulci,

    Welcome to the MSDN Forum.

    I have test your code in a winform application:

    using System;
    using System.Windows.Forms;
    using System.Threading;
    using System.Collections.Generic;
    
    namespace WinFormsApp_GC
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Test(int c)
            {
                Console.WriteLine("Test start.");
    
                List<TestObject> list = new List<TestObject>();
                for (int i = 0; i < c; i++)
                {
                    list.Add(new TestObject());
                }
    
                Console.WriteLine("End of adding. Added: {0} items.", c);
                Console.ReadLine();
    
                foreach (TestObject obj in list)
                {
                    obj.Dispose();
                }
    
                list = null;
                Console.WriteLine("Dispose end.");
    
                Console.ReadLine();
                GC.Collect(0, GCCollectionMode.Forced);
                GC.Collect(1, GCCollectionMode.Forced);
                GC.Collect(2, GCCollectionMode.Forced);
                GC.WaitForPendingFinalizers();
                Console.WriteLine("After GC collect");
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                Test(9000);
            }
        }
    
    
        public class TestObject : IDisposable
        {
            public ManualResetEvent mr1 = new ManualResetEvent(true);
            public ManualResetEvent mr2 = new ManualResetEvent(false);
    
            public void Dispose()
            {
                mr1.Dispose();
                mr2.Dispose();
            }
        }
    }

    When the form show, the memory usage is more than 6MB, and after I click on the button, it raises to more than 7MB, and click again, it raise to more than 8MB, but during the next click, the memory usage doesn't raise so obviously. I think this can prove the GC works, it collects the unused memory.

    So you can try to improve your memory buffer to avoid this situation.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    Friday, August 10, 2012 10:45 AM
    Moderator

All replies

  • See this webpage below.  dispose says optional release while finalize say release.  May be change dispose to finaliZe?

    http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent_members(v=vs.71)


    jdweng

    Wednesday, August 8, 2012 12:42 PM
  • Dispose has nothing to do with memory consumption unless you are deallocating unmanaged memory, which you are not really doing here.

    The purpose of dispose is to release resources, like file handles.  (Or unmanaged memory).

    I think you are misunderstanding the concept of a process memory working set.  A process will hold on to memory it is using and also the memory it has recently used.  A process will only return memory to the operating system when the operating system asks for more memory than the system currently has physically available  The sophistication of the process memory model in the Windows kernel is beyond the scope of this discussion, so let's just put it simply:

    Your test is not valid.  Memory working set does not equate to actual memory currently used.

    Evan

    Wednesday, August 8, 2012 1:51 PM
  • Evan : I slightly disagree with your statement.  When I write dispose functions I make sure I relsease all resource managed and unmangaged.  I expect whenever any of my classes are dispose I want to make sure there aren't any memory leaks so I realse ALL memory.  I would assume Microsoft does the same.

    jdweng

    Wednesday, August 8, 2012 2:15 PM
  • First, you are implementing the Dispose() pattern incorrectly. You need to have a Finalizer and you also need to call GC.SuppressFinalize(). See here.

    Second, I would look more at your performance counters for the generations and large object heap and how much memory is allocated to each generation, rather than the working set.

    Finally, the collection will almost certainly be in the Large Object Heap, which has slightly different rules. Once the memory is allocated on the LOH, it isn't released as it is in the Small Object Heap - the LOH is never compacted. See this MSDN article for some of the gory details. You may also try to use List.Clear() rather than setting it to null but I don't think that'll make any difference. It's still in the LOH.


    DevBiker (aka J Sawyer)
    Microsoft MVP - Sql Server (StreamInsight)


    Ruminations of J.net


    If I answered your question, please mark as answer.
    If my post was helpful, please mark as helpful.

    Wednesday, August 8, 2012 2:44 PM
  • OK, This small program was only my test, which I would like to use to learn something about releasing of consumed memory. Now I know that referring to the WorkingSet value is not a good idea. The origin of my problem is a little bit different, maybe I will try to describe it here shortly.

    I have a quite large ASP.NET application, which seems that have properly implemented the Microsoft recommended IDisposable pattern. I simulate some big amount of work, which mainly relay on creating some finite number of threads, working with finite number of files and instantiating really a lot of managed objects. I observe on the w3wp.exe process the consumed memory. The problem is that leaving my app for 1-2 hours with such simulated job cause that w3wp.exe memory is growing and growing. It is growing to some level and then it is making automatically the IISRESET - I suppose that here he decided that he do not have more memory. After that of course consumed RAM is released.

    I would like to avoid such situation, because I think that this is a memory leak, that's way I have prepared such Console application test. But now I am confused more than I was at the beginning.

    Thank you for help.

    Thursday, August 9, 2012 5:27 AM
  • Hi Kulci,

    Welcome to the MSDN Forum.

    I have test your code in a winform application:

    using System;
    using System.Windows.Forms;
    using System.Threading;
    using System.Collections.Generic;
    
    namespace WinFormsApp_GC
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Test(int c)
            {
                Console.WriteLine("Test start.");
    
                List<TestObject> list = new List<TestObject>();
                for (int i = 0; i < c; i++)
                {
                    list.Add(new TestObject());
                }
    
                Console.WriteLine("End of adding. Added: {0} items.", c);
                Console.ReadLine();
    
                foreach (TestObject obj in list)
                {
                    obj.Dispose();
                }
    
                list = null;
                Console.WriteLine("Dispose end.");
    
                Console.ReadLine();
                GC.Collect(0, GCCollectionMode.Forced);
                GC.Collect(1, GCCollectionMode.Forced);
                GC.Collect(2, GCCollectionMode.Forced);
                GC.WaitForPendingFinalizers();
                Console.WriteLine("After GC collect");
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                Test(9000);
            }
        }
    
    
        public class TestObject : IDisposable
        {
            public ManualResetEvent mr1 = new ManualResetEvent(true);
            public ManualResetEvent mr2 = new ManualResetEvent(false);
    
            public void Dispose()
            {
                mr1.Dispose();
                mr2.Dispose();
            }
        }
    }

    When the form show, the memory usage is more than 6MB, and after I click on the button, it raises to more than 7MB, and click again, it raise to more than 8MB, but during the next click, the memory usage doesn't raise so obviously. I think this can prove the GC works, it collects the unused memory.

    So you can try to improve your memory buffer to avoid this situation.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    Friday, August 10, 2012 10:45 AM
    Moderator
  • First, put your app in it's own app pool ... this will isolate it from anything else on the box.

    Start looking into the performance counters for .NET. Check out how much is in each generation and the large object heap. If Gen2 is large, then you have long-lived objects, which is usually (especially for a web app) not a good thing. You will also want to look at finalization survivors ... these will be objects that are not properly disposed either by not calling Dispose() or by not included GC.SuppressFinalize() in their Dispose() method.

    Certain aspects of your architecture may also be causing this. How heavily are you using caching? Are you using Sessions? Do you have static, application-wide variables?

    It does sound to me like there is a memory leak. One thing that you can do ... and this is not a fix but a way to test ... is to have a page that calls GC.Collect() a couple of times (it actually takes 2 rounds for the objects that need finalization) and see if that drops the memory. If so, then you can be sure that there is a leak somewhere. If not ... then start looking at what you are holding on to at the application/session level.


    DevBiker (aka J Sawyer)
    Microsoft MVP - Sql Server (StreamInsight)


    Ruminations of J.net


    If I answered your question, please mark as answer.
    If my post was helpful, please mark as helpful.

    Friday, August 10, 2012 12:36 PM