Disposal and garbage collection in C#

In this article, I will show you how to avoid memory leaks in c# by implementing IDisposable pattern with a real-world example.

Object lifetime in C#

  • The developer should use the “new” keyword to allocate memory to an object.
  • Objects are allocated onto the managed heap, where the runtime automatically deallocates them at “some time in the future”
  • Garbage collection is automated in C#-Allocate an object onto the managed heap using the new keyword and forget about it.

What new keyword does?

var calss=new MyClass();

  • Calculate the total amount of memory required for the object. Examine the managed heap to ensure enough room for the object
  • Return the reference to the caller, advance the next object pointer to point to the next available slot on the managed heap

Allocate Objects onto one of two heaps

  • Small-Object Heap(SOH)-Objects<85K

Small objects are always allocated in generation 0 and, depending on their lifetime, maybe promoted to generation 1 or generation2. On the other hand, large objects are always allocated in generation 2.

  • Large Object Heap(LOH)- Objects>=85k

Large objects belong to generation 2 because they are collected only during a generation 2 collection. When a generation is collected, all its younger generation(s) are also collected.

If the managed heap does not have sufficient memory to allocate a requested object, a garbage collection will occur.

How Garbage collection works?

  • The garbage collector searches for managed objects that are referenced in managed code.
  • The garbage collector attempts to finalize unreachable objects
  • The garbage collector frees objects that are unmarked and reclaims their memory

Object graph represents each reachable object on the heap

The System.GC type

Provide a set of static methods for interacting with garbage collection. Use this type when you are creating types that make use of the unmanaged resources.

How to avoid Memory Leak?

  • Object generations Each object on the heap is assigned to a specific “generation” (0-2)
  • Generation 1: newly allocated objects -
  • Generation 2: objects that survived a garbage collection
  • Generation 3: objects that survived more than one garbage collection
  • The garbage collector first investigates generation 0 objects. If marking and sweeping these objects can result in the required amount of free memory, any surviving objects’ generation is promoted by 1.

Building Disposable Object

Another approach to handle an object’s cleanup. Implement the IDisposable interface

  • Object users should manually call Dispose() before allowing the object reference to drop out of scope -
  • Structures and classes can both support IDisposal (unlike overriding Finalize())

Let’s implement the IDisposable pattern and see the results. The template for IDisposable the pattern is as shown below

public class MyClass : IDisposable
{

	~MyClass()
	{
		Dispose(disposing: false);
	}
	protected virtual void Dispose(bool disposing)
	{
	}
	public void Dispose()
	{
		Dispose(disposing: true);
		GC.SuppressFinalize(this);
	}

}

The dispose pattern requires implementing two Dispose methods but does not force overriding the Object Finalize method. The object consumer invokes the publicly accessible Dispose method, which serves two purposes: it frees unmanaged resources and, more importantly, it indicates that the finalizer does not need to run.{alertSuccess}

Let’s create a class which implements IDisposable pattern and also add some unmanaged resource. For unmanaged resource, I am using Marshal.AllocHGlobal(size) And for the managed resource, I am using FileStream. The code is self-explanatory.

 class MemoryLeakDemo : IDisposable
    {
        private readonly IntPtr _handle;
        private MemoryStream stream;
        private bool _disposed;

        public MemoryLeakDemo(int size)
        {
            // Unmanaged 
            _handle = Marshal.AllocHGlobal(size);
            // Managed
            CreateByteArray();

        }
        public MemoryStream CreateByteArray()
        {
            stream = new MemoryStream();
            byte[] buffer = new byte[1024];
            stream.Read(buffer, 0, buffer.Length);
            return stream;
        }

        ~MemoryLeakDemo()
        {
            Dispose(disposing: false);

        }
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)
            {
                // Remove Managed Type
                stream.Dispose();
            }
            // Remove UnManaged type
            Marshal.FreeHGlobal(_handle);
            _disposed = true;

        }
        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }

Finalizer (+ Freachable) Queue

If an object type overrides the Finalize method, the garbage collector adds an item for each instance to the finalization queue, an internal structure. This queue holds entries for all objects with a declared finalizer in the managed heap. Before the garbage collector can start recovering the instance memory, this finalization function must run.

![](Figure 5 A Heap with Many Objects

GC doesn’t call the Finalize method directly from this queue. Instead, it removes object reference from the finalizer queue and puts it on the reachable queue. Each object reference in the freachable queue identifies an object ready to have its Finalize method called.{alertInfo}

Let’s analyze the code to see that which code is better, AutoDispose or Force Garbage Collector. To compare this, I am using the Benchmark NuGet package.

class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Performance>();

        }

    }
    [MemoryDiagnoser]
    public class Performance
    {
      
        [Benchmark(Baseline =true)]
        public void AutoDisposedUsing()
        {
            var list = new List<MemoryLeakDemo>();
            for (int i = 0; i < 10_00; i++)
            {
                list.Add(new MemoryLeakDemo(1024 * 1024));
            }
            list.ForEach(x => x.Dispose());

        }
        [Benchmark]
        public void ForceGarbadgeCollector()
        {
            var list = new List<MemoryLeakDemo>();
            for (int i = 0; i < 10_00; i++)
            {
                list.Add(new MemoryLeakDemo(1024 * 1024));
            }
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();


        }
    }

As you can see in the above result, no object collection happens when we call the disposal method, but many objects are collected by the garbage collector in other cases.

Gen 0     : GC Generation 0 collects per 1000 operations
Gen 1     : GC Generation 1 collects per 1000 operations
Gen 2     : GC Generation 2 collects per 1000 operations

Please do not post any spam link in the comment box😊

Post a Comment (0)
Previous Post Next Post