Thursday, 17 December 2009

Watching the .NET Garbage Collector

Here's an interesting use of SciTech's .NET Memory Profiler – watching how objects are handled by the .NET CLR garbage collector.

The sample code create 4 objects – a simple class "Simple", a class that implements IDisposable "Disposable", and a class that also implements a destructor "Destructable".

.NET Memory Profiler can generate a real-time graph of memory allocations. I've instrumented the sample code using SciTech's API to add comments to the graph so you can match the code execution path against the X axis (time). The object instance count is mapped to the Y axis.

image

See how 2 instances of the "Destructable" class existed around the 10 second mark – then one was released at the first GC, then the 2nd (which has the destructor) is only released after the 2nd GC.

using System;
using System.Diagnostics;
using SciTech.NetMemProfiler;

namespace MemoryTesting
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            MemProfiler.FullSnapShot("Start");

            CreateSimple();

            CreateDisposable();
            System.Threading.Thread.Sleep(1000);

            CreateDestructableAndDispose();
            System.Threading.Thread.Sleep(1000);

            CreateDestructable();
            System.Threading.Thread.Sleep(1000);

            MemProfiler.AddRealTimeComment("GC");
            GC.Collect();

            System.Threading.Thread.Sleep(2000);
            MemProfiler.AddRealTimeComment("WaitForPendingFinalizers");
            GC.WaitForPendingFinalizers();

            System.Threading.Thread.Sleep(2000);

            MemProfiler.AddRealTimeComment("GC");
            GC.Collect();

            System.Threading.Thread.Sleep(1000);

            MemProfiler.AddRealTimeComment("End");
            MemProfiler.FullSnapShot();

        }

        private static void CreateSimple()
        {
            MemProfiler.AddRealTimeComment("Create Simple");

            var a = new Simple();
            a.Something += OnEventHandler;
            a.Data = "hey";

            a.Something -= OnEventHandler;

        }

        private static void CreateDisposable()
        {
            MemProfiler.AddRealTimeComment("Create Disposable");

            var a = new Disposable();
            a.Something += OnEventHandler;
            a.Data = "ho";
            System.Threading.Thread.Sleep(1000);
            a.Something -= OnEventHandler;

            a.Dispose();
        }

        private static void CreateDestructableAndDispose()
        {
            MemProfiler.AddRealTimeComment("Create Destructable and Dispose");

            var a = new Destructable();
            a.Something += OnEventHandler;
            a.Data = "haha";
            System.Threading.Thread.Sleep(1000);
            a.Something -= OnEventHandler;

            a.Dispose();
        }

        private static void CreateDestructable()
        {
            MemProfiler.AddRealTimeComment("Create Destructable");

            var a = new Destructable();
            a.Something += OnEventHandler;
            a.Data = "haha";
            System.Threading.Thread.Sleep(1000);
            a.Something -= OnEventHandler;
            //a.Dispose();
        }

        private static void OnEventHandler(object sender, EventArgs e)
        {
            Debug.WriteLine("Something Fired");
        }
    }

    public class Simple
    {
        private string _data;

        public string Data
        {
            get { return _data; }
            set
            {
                _data = value;
                OnSomething(null);
            }
        }

        public event EventHandler Something;

        protected virtual void OnSomething(EventArgs e)
        {
            EventHandler handler = Something;
            if (handler != null) handler(this, e);
        }
    }


    public class Disposable : Simple, IDisposable
    {
        #region IDisposable Members

        public void Dispose()
        {
            System.Threading.Thread.Sleep(1000);

            MemProfiler.AddRealTimeComment("Dispose");

            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        protected virtual void Dispose(bool disposing)
        {
        }
    }

    public class Destructable : Disposable
    {
        ~Destructable()
        {
            System.Threading.Thread.Sleep(1000);

            MemProfiler.AddRealTimeComment("Destructing");

            Dispose(false);
        }
    }
}

No comments: