DCSIMG
GC Helper for Obtaining Live Instances of a Type, or How I Implemented GC.GetAliveInstancesOf<T>() - All Your Base Are Belong To Us

All Your Base Are Belong To Us

Mostly .NET internals and other kinds of gory details

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

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.

Comments

GC Helper for Obtaining Live Instances of a Type, or How I Implemented GC.GetAliveInstancesOf() « Jasper Blog said:

Pingback from  GC Helper for Obtaining Live Instances of a Type, or How I Implemented GC.GetAliveInstancesOf() &laquo; Jasper Blog

# August 18, 2009 9:27 AM

Reflective Perspective - Chris Alcock » The Morning Brew #414 said:

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #414

# August 18, 2009 10:16 AM

Dew Drop – August 18, 2009 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop &#8211; August 18, 2009 | Alvin Ashcraft's Morning Dew

# August 18, 2009 4:10 PM

Shiva said:

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?

# August 24, 2009 4:06 PM

Patrick Smacchia said:

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

# August 25, 2009 7:20 PM

The Week in Code (X) « Sgt. Conker said:

Pingback from  The Week in Code (X) &laquo; Sgt. Conker

# October 15, 2009 4:17 PM

Alois Kraus said:

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 geekswithblogs.net/.../150026.aspx.

It is hosted on Codeplex at wmemoryprofiler.codeplex.com

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

Yours,

 Alois Kraus

# June 23, 2012 10:53 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: