none
Unexplained behavior of GC and strong/weak references. RRS feed

  • Question

  • Hello.

    I've set up this test console application, running on .NET 3.5 SP1.

     

    I wanted to test the behavior of weak references inside finalizers, so I've created this test application, but it does not behave as I would expect - The GC.Collect statements do not really collect the ResContainer object, and I cannot understand why.

     

    This is the code I've used:

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace ResurrectionTest
    {
      public class ResTest
      {
        private List<ResContainer> _resContainers;
    
        private object _myObj;
    
        private class SampleObject
        {
          public object Tag { get; set; }
        }
    
        private class ResContainer
        {
          public WeakReference WeakRef { get; set; }
    
          public List<ResContainer> ContainingList { get; set; }
    
          public object Tag { get; set; }
    
          ~ResContainer()
          {
            object obj = WeakRef.Target;
            if (obj != null)
            {
              ContainingList.Add(this);
              Console.WriteLine("Container resurrected");
    
              GC.ReRegisterForFinalize(this);
            }
            else
            {
              Console.WriteLine("Container destroyed");
            }
          }
        }
    
        public void Run()
        {
          Console.WriteLine("Preparing variables...");
    
          _myObj = new SampleObject() { Tag = "HeY!" };
          _resContainers = new List<ResContainer>();
          _resContainers.Add(new ResContainer() { ContainingList = _resContainers, WeakRef = new WeakReference(_myObj, true), Tag = new SampleObject() { Tag = _myObj } });
    
          Console.WriteLine("Clearing list");
          _resContainers.Clear();
    
          Console.WriteLine("Inducing GC - container should be resurrected as _myObj still points to the object");
          GC.Collect();
          GC.WaitForPendingFinalizers();
          // No finalizer is called - how come?
    
          Console.WriteLine("Setting _myObj to null");
          _myObj = null;
    
          Console.WriteLine("Inducing GC - the container should be destroyed");
          GC.Collect();
          GC.WaitForPendingFinalizers();
          // No finalizer is called here as well
    
          Console.WriteLine("Done.");
          Console.ReadLine();
        }
      }
    
      static class Program
      {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main()
        {
          new ResTest().Run();
        }
      }
    }
    
    

    Can anyone explain this behavior?

     

    Thanks,

    Shahar.

    Saturday, September 18, 2010 7:39 AM

Answers

  • > Have you any idea how come the container is destroyed here?

    You called _resContainers.Clear() shortly before, thus making the ResContainer eligible for garbage collection.  I suppose you are really wondering why you see "Container destroyed" instead of "Container resurrected"...  This is because the WeakReference itself got finalized!  WeakRef.Target is null if the target is collected OR the WeakReference itself has been finalized.  It works this way because this is the mechanism by which WeakReference releases its hold on the object.

    This behavior is not documented.  In general, you are not supposed to call methods on objects which might have been finalized.  This greatly limits what you can validly do in a finalizer since the finalization order is also unspecified.  That WeakReference object qualifies, hence the behavior should be considered unspecified and you should not rely on it in any way.

    The following code shows how to get "Container resurrected" to run.  I had to put a GC.KeepAlive in too.  The GC is very, very aggressive!  (Read about GC.KeepAlive here: http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx)

          _myObj = new SampleObject() { Tag = "HeY!" };
          _resContainers = new List<ResContainer>();
          WeakReference wr = new WeakReference(_myObj, true);
          _resContainers.Add(new ResContainer() { ContainingList = _resContainers, WeakRef = wr, Tag = new SampleObject() { Tag = _myObj } });
          Console.WriteLine("Clearing list");
          _resContainers.Clear();
          Console.WriteLine("Inducing GC - container should be resurrected as _myObj still points to the object");
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.KeepAlive(wr);
    

    Prints:

    Preparing variables...
    Clearing list
    Inducing GC - container should be resurrected as _myObj still points to the object
    Container resurrected
    Setting _myObj to null
    Inducing GC - the container should be destroyed
    Container destroyed
    Done.
    

     

    • Marked as answer by Shahar W Monday, September 20, 2010 6:20 PM
    Saturday, September 18, 2010 10:43 PM

All replies

  • I'm running Visual Studio 2010 with .NET 3.5 Client and your code works well:

    1) You should run in release mode since GC won't work as expected in Debug mode.

    2) Remove the Console.ReadLine(); and thus the destructor should be called as expected.

    Saturday, September 18, 2010 8:17 AM
  • Hi.

    I've ran it in release mode and the result is the same - I was expecting to see the finalizer's WriteLine statements during the program execution, and not just when the application terminates.

    However, I can see no console output from the finalizer.

     

    So, I don't quite understand it yet..

     

    PS:

    I'm using VS 2008 SP1 on Windows 7 x64.

    Saturday, September 18, 2010 8:59 AM
  • Did you make sure that Build Configuration is set to Release Mode ?
    Saturday, September 18, 2010 9:55 AM
  • Ok, here's the funny thing:

    I ran it using VS 2008 in release mode, and it showed no finalizer output.

    However, when I executed the compiled assembly directly, I got the following output:

     

    Preparing variables...

    Clearing list

    Inducing GC - container should be resurrected as _myObj still points to the object

    Container destroyed

    Setting _myObj to null

    Inducing GC - the container should be destroyed

    Done.




    Have you any idea how come the container is destroyed here?

    Thanks again,
    Shahar

     

    Saturday, September 18, 2010 11:50 AM
  • > Have you any idea how come the container is destroyed here?

    You called _resContainers.Clear() shortly before, thus making the ResContainer eligible for garbage collection.  I suppose you are really wondering why you see "Container destroyed" instead of "Container resurrected"...  This is because the WeakReference itself got finalized!  WeakRef.Target is null if the target is collected OR the WeakReference itself has been finalized.  It works this way because this is the mechanism by which WeakReference releases its hold on the object.

    This behavior is not documented.  In general, you are not supposed to call methods on objects which might have been finalized.  This greatly limits what you can validly do in a finalizer since the finalization order is also unspecified.  That WeakReference object qualifies, hence the behavior should be considered unspecified and you should not rely on it in any way.

    The following code shows how to get "Container resurrected" to run.  I had to put a GC.KeepAlive in too.  The GC is very, very aggressive!  (Read about GC.KeepAlive here: http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049634.aspx)

          _myObj = new SampleObject() { Tag = "HeY!" };
          _resContainers = new List<ResContainer>();
          WeakReference wr = new WeakReference(_myObj, true);
          _resContainers.Add(new ResContainer() { ContainingList = _resContainers, WeakRef = wr, Tag = new SampleObject() { Tag = _myObj } });
          Console.WriteLine("Clearing list");
          _resContainers.Clear();
          Console.WriteLine("Inducing GC - container should be resurrected as _myObj still points to the object");
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.KeepAlive(wr);
    

    Prints:

    Preparing variables...
    Clearing list
    Inducing GC - container should be resurrected as _myObj still points to the object
    Container resurrected
    Setting _myObj to null
    Inducing GC - the container should be destroyed
    Container destroyed
    Done.
    

     

    • Marked as answer by Shahar W Monday, September 20, 2010 6:20 PM
    Saturday, September 18, 2010 10:43 PM
  • Wow. I knew finalizers were messy, but I guess I didn't realize just how messy :)

     

    Thanks a lot for the clarification, you helped me greatly.

    Monday, September 20, 2010 6:22 PM