GC Helper for Obtaining Live Instances of a Type, or How I Implemented GC.GetAliveInstancesOf<T>()

August 17, 2009

3 comments

Patrick humbly asks for a method tentatively called GC.GetAliveInstancesOf<T>() that returns a collection of referenced instances of a specific type.

While useful, this is something that doesn’t belong in the .NET framework, much more so in the API of the garbage collector, but it’s still very useful for diagnostic scenarios (e.g. SOS yields this information easily through the !dumpheap –type command).

The following is a quick and dirty suggestion at an implementation that “automagically” tracks live object instances and returns them on demand. The idea is that the object’s constructor can add a weak reference to the object to a static hash table, and the object’s finalizer can remove the weak reference from that table. Because we’re using weak references, they in themselves do not prevent the objects from being collected.

The code is rather long for a blog post and includes four significant parts:

  • SynchronizedWeakHashSet<T> is a collection of weak references that is thread-safe and offers only a small subset of operations. The Remove operation is especially inefficient as it iterates through all the objects, and to resolve this we would have to introduce a token that uniquely identifies the object without storing its reference.
  • InstanceTracker<T> is designed as a base class for objects that require instance tracking. Its constructor and finalizer do the actual magic of storing the object’s reference, and the GetLiveInstances static method waits for GC and finalizers to complete and then returns the live objects.
  • MyClass is a sample class that conditionally derives from InstanceTracker<MyClass> so that its instances are tracked at runtime.
  • The main program creates three instances of MyClass and shows the live ones (there should be only one live instance, rooted at the static member variable).

public sealed class SynchronizedWeakHashSet<T> where T : class

{

    private readonly object _syncRoot = new object();

    private readonly HashSet<WeakReference> _hash =
        new HashSet<WeakReference>();

 

    public int Count

    {

        get

        {

            lock (_syncRoot)

                return _hash.Count;

        }

    }

 

    public T[] ToArray()

    {

        lock (_syncRoot)

        {

            T[] arr = new T[_hash.Count];

            int curr = 0;

            var toRemove = new List<WeakReference>();

            foreach (WeakReference wr in _hash)

            {

                T inst = wr.Target as T;

                if (inst == null)

                {

                    //Remove any dead references.

                    toRemove.Add(wr);

                }

                else

                {

                    arr[curr++] = inst;

                }

            }

            foreach (WeakReference wr in toRemove)

                _hash.Remove(wr);

            return arr;

        }

    }

 

    public void Add(T instance)

    {

        lock (_syncRoot)

            _hash.Add(new WeakReference(instance));

    }

 

    public void Remove(T instance)

    {

        //This is not efficient, but to do better we need a token

        //that can identify the weak reference for us.

        lock (_syncRoot)

        {

            var toRemove = new List<WeakReference>();

            foreach (WeakReference wr in _hash)

            {

                object target = wr.Target;

                if (target != null &&
                    Object.ReferenceEquals(target, instance))

                {

                    toRemove.Add(wr);

                    break;

                }

                else if (target == null)

                {

                    //Remove any dead references.

                    toRemove.Add(wr);

                }

            }

            foreach (WeakReference wr in toRemove)

                _hash.Remove(wr);

        }

    }

}

 

#if DEBUG

public abstract class InstanceTracker<T>
    where T : InstanceTracker<T>

{

    private static readonly SynchronizedWeakHashSet<T> _instances
        = new SynchronizedWeakHashSet<T>();

 

    public static IEnumerable<T> GetLiveInstances()

    {

        GC.Collect();

        GC.WaitForPendingFinalizers();

 

        return _instances.ToArray();

    }

 

    protected InstanceTracker()

    {

        _instances.Add((T)this);

    }

    ~InstanceTracker()

    {

        _instances.Remove((T)this);

    }

}

#endif

 

class MyClass

#if DEBUG

    : InstanceTracker<MyClass>

#endif

{

    public int Id { get; set; }

}

 

class Program

{

    static MyClass _root;

 

    static void Foo()

    {

        _root = new MyClass { Id = 5 };

        _root = new MyClass { Id = 3 };

        _root = new MyClass { Id = 4 };

    }

 

    static void Main(string[] args)

    {

        Foo();

        foreach (MyClass mc in
                 InstanceTracker<MyClass>.GetLiveInstances())

            Console.WriteLine(mc.Id);

    }

}

This code is conditionally compiled only into the Debug build. (There are obviously cleaner alternatives, the key is that the finalizer must not be present in Release builds because of its performance hit.)

Remember that this is nothing more than a quick and dirty demo, but it proves the point and might turn out useful for diagnostic purposes.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

3 comments

  1. ShivaAugust 24, 2009 ב 4:06 PM

    Thank you for this excellent post!

    Reg. optimizing the Remove() method, how about InstanceTracker holding a reference to the WeakReference added to the hash table? The held instance can be passed to the Remove method.

    Also, is the “removal of dead references” required during Remove call? Is it possible at all that there exist some dead WeakReference in the hash table in spite of we removing the WeakReference in the finalizer?

    Reply
  2. Patrick SmacchiaAugust 25, 2009 ב 7:20 PM

    Sacha, I wanted to let you know that I tried your solution and it is working like a charm :o) Thanks!

    Reply
  3. Alois KrausJune 23, 2012 ב 10:53 PM

    I did create something similar which does not impose any limitions to your object design and does work with any type. It is basically a Memory Profiler which does not use the profiling Api but Windbg and SOS.

    See http://geekswithblogs.net/akraus1/archive/2012/06/22/150026.aspx.

    It is hosted on Codeplex at https://wmemoryprofiler.codeplex.com/

    This does come in very handy to check for memory leaks during integration tests.

    Yours,
    Alois Kraus

    Reply