0

I am Eager to know how to do memory management in c# application.

My application is not releasing memory even I dispose objects and make it nullify. For testing purpose, I have created sample application as described below.

An application has two buttons 1) Consume Memory 2) Release Memory

By clicking on Consume Memory I am going to create and add 500 Memory Stream objects into List.

By clicking on Release Memory I am going to dispose all Memory Stream object and nullify that List too. and Collect garbage collection.

But when I start an application at that time my task manager will show me 8.6 MB Memory usage. When I press Consume Memory at that time task manager will show me 679.6 MB Memory Usage. When I Press Release Memory at that time task manager will show 680.0 MB Memory usage.

How can I forcefully release memory?

Code

public partial class MainWindow : Window
{
    List<System.IO.MemoryStream> MemoryStreamCollection = new List<System.IO.MemoryStream>();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void ConsumeMemory_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < 500; i++)
        {
            MemoryStreamCollection.Add(new System.IO.MemoryStream(System.IO.File.ReadAllBytes(@"D:\TestPDF.pdf")));
        }

        MessageBox.Show("Done");
    }

    private void ReleaseMemory_Click(object sender, RoutedEventArgs e)
    {
        foreach (System.IO.MemoryStream memoryStream in MemoryStreamCollection)
        {
            memoryStream.Dispose();
        }

        MemoryStreamCollection = null;

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

        MessageBox.Show("Done");
    }
}

Screenshot when an application starts

enter image description here

Screenshot after consume memory button click.

enter image description here

Screenshot after release memory button click.

enter image description here

Darshan Faldu
  • 1,471
  • 2
  • 15
  • 32
  • Do you need to hold onto all the objects and once, then release them in one go? If not, you could try seeing how it behaves if you use `using` blocks to create them so they're automatically released when they're no longer needed. – Fabulous Jul 09 '18 at 13:47
  • Have you tried calling GC.Collect() without the optional parameters? – StefanFFM Jul 09 '18 at 13:47
  • 2
    By adding `MemoryStreamCollection.Clear();` before `MemoryStreamCollection = null;` the memory leak will no longer occur. I have successfully tested it with your sample code. But I have no clue why this List does not get collected when you forcibly call GC. Even with `WaitForPendingFinalizers`, it does not get collected. – FrankM Jul 09 '18 at 14:03
  • Extending @bommelding comment, `MemoryStream.Dispose()` or `MemoryStream.Close()` don't really need to be called. They lead to the same mathod and they don't have any unmanaged resources. You can read more [here](https://stackoverflow.com/questions/4274590/memorystream-close-or-memorystream-dispose) – Thadeu Fernandes Jul 09 '18 at 14:18

3 Answers3

1

Why are you trying to do this? The garbage collector will collect the garbage when it feels like it. It is often a bad idea to try to manage memory manually in C#.

That said, you could try using the following little trick I use sometime when running benchmarks that I don't want to be interrupted by the GC (cleaning up all the memory freed from last benchmark) --

GC.Collect();  

GC.WaitForPendingFinalizers(); 

GC.Collect(); 

GC.WaitForPendingFinalizers();
Babbillumpa
  • 1,854
  • 1
  • 16
  • 21
Chris S.
  • 161
  • 1
  • 7
0

It is not recommended to call gc explicitly, but if you call

System.GC.Collect();
System.GC.WaitForPendingFinalizers();

It will call GC explicitly throughout your code, don't forget to call GC.WaitForPendingFinalizers(); after GC.Collect().

WaitForPendingFinalizers does not always gives a better performance, it simply blocks until all objects in the finalisation queue have finalised. If you want those objects to be collected then you need call System.GC.Collect a second time.

0

When you add a third button called GarbageCollect and modify your code as follows, it will work:

using System;
using System.Collections.Generic;
using System.Windows;

namespace WpfApplication9
{
    public partial class MainWindow : Window
    {
        List<System.IO.MemoryStream> MemoryStreamCollection = new List<System.IO.MemoryStream>();

        public MainWindow()
        {
            InitializeComponent();
        }

        private void ConsumeMemory_Click(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < 500; i++)
            {
                MemoryStreamCollection.Add(new System.IO.MemoryStream(System.IO.File.ReadAllBytes(@"C:\temp\bpmn.png")));
            }

            MessageBox.Show("Done");
        }

        private void ReleaseMemory_Click(object sender, RoutedEventArgs e)
        {
            foreach (System.IO.MemoryStream memoryStream in MemoryStreamCollection)
            {
                memoryStream.Dispose();
            }

            MemoryStreamCollection = null;

            MessageBox.Show("Done");
        }

        private void GarbageCollect_Click(object sender, RoutedEventArgs e)
        {
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

            MessageBox.Show("Done");
        }
    }
}

The reason is that in your code, you are calling GC.Collect while the temporary variables of the foreach loop still are on the stack:

private void ReleaseMemory_Click(object sender, RoutedEventArgs e)
{
    foreach (System.IO.MemoryStream memoryStream in MemoryStreamCollection)
    {
        memoryStream.Dispose();
    }

    MemoryStreamCollection = null;

    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

    MessageBox.Show("Done");
}

By moving GC.Collect out of the function scope, the List and its iterator will no longer be on the local stack and GC can successfully do its job.

This is the situation in your sample code before the MessageBox.Show("Done") line in the ReleaseMemory_Click() method, as shown in a memory profiler:

screen shot

As you can see, the List and one MemoryStream is held by the stack frame of the ReleaseMemory_Click() method.

FrankM
  • 1,007
  • 6
  • 15
  • Thanks, @FrankM It works but in any c# application which things I need to take care to avoid this kind of glitch. – Darshan Faldu Jul 09 '18 at 14:50
  • My advice: test, test, test... Especially with large data sets. And if you encounter any memory leak, use the memory profiler in Visual Studio or a 3rd party tool for diagnosing the root cause of the leak. Regarding types with `IDisposable` interface, try to use the `using (...)` pattern whenever possible. And if you are a WPF programmer, carefully read [Finding Memory Leaks in WPF](https://blogs.msdn.microsoft.com/jgoldb/2008/02/04/finding-memory-leaks-in-wpf-based-applications/); this article has "saved my life" a dozen times. – FrankM Jul 09 '18 at 14:53