{"id":471,"date":"2009-06-11T14:54:20","date_gmt":"2009-06-11T19:54:20","guid":{"rendered":"http:\/\/www.thegatesofdawn.ca\/wordpress\/?p=471"},"modified":"2021-04-12T23:44:11","modified_gmt":"2021-04-13T04:44:11","slug":"net-garbage-collector-making-me-batty","status":"publish","type":"post","link":"https:\/\/www.thegatesofdawn.ca\/wordpress\/posts\/2009\/06\/11\/net-garbage-collector-making-me-batty\/","title":{"rendered":".NET garbage collector making me batty"},"content":{"rendered":"<p>I&#8217;ve spent a few days trying to track down a garbage collection problem in some code I&#8217;ve been working on.\u00a0 I finally figured it out.  Along the way, I&#8217;ve learned more about .NET garbage collection than I ever wanted to know, and discovered a useful tool to help with these kinds of problems.<\/p>\n<p><!--more--><\/p>\n<p>I have a bunch of <code>SharedConnection<\/code> objects.\u00a0 I keep them in a cache, in the form of a <code>Dictionary&lt;string,WeakReference&gt;<\/code>.  The cache is filled with <code>WeakReference<\/code>s, because I don&#8217;t want the cache to keep the <code>SharedConnection<\/code>s from being garbage-collected when nobody else is referencing them anymore.<\/p>\n<p>But it wasn&#8217;t working.  Unreferenced <code>SharedConnection<\/code> objects lived forever, even when I explicitly called <code>GC.Collect()<\/code>.  I know, the first rule of <code>GC.Collect()<\/code> is &#8220;Don&#8217;t call <code>GC.Collect()<\/code>.&#8221;  But I have a good reason.  And unreferenced <code>SharedConnection<\/code> objects were not getting collected.  Why not?<\/p>\n<p>I found a program called <a href=\"http:\/\/memprofiler.com\/\">.NET Memory Profiler<\/a>, which has been very helpful.  What it does is takes a snapshot of the entire heap, and then lets you browse around it.  You can find all instances of a given type.  You can find everything else that the object references, directly or indirectly.  And you can find everything that references the object, directly or indirectly.  This last one is the one that really helps with sorting out garbage-collection problems.<\/p>\n<p>The best explanation of the .NET garbage collector I&#8217;ve found is <a href=\"http:\/\/www.csharphelp.com\/archives2\/archive297.html\">here<\/a>.  The important thing is that it does not use reference counting.  That means it does not get tricked when two objects circularly reference each other.  Such objects can still be garbage collected, if there are no other references.  Any object that is unreachable from a &#8220;root&#8221; is a candidate for collection.  If an object can&#8217;t be reached from a root, then it can&#8217;t be reached at all, ever, by anyone, and might as well be collected.  That includes little islands of mutually-referencing objects that can&#8217;t be reached from anywhere else.  They&#8217;re gone.  Or at least they should be.<\/p>\n<p>But my <code>SharedConnection<\/code> objects were not being collected.  .NET Memory Profiler showed me why.  The problem is that my <code>SharedConnection<\/code> objects include a <code>System.Timers.Timer<\/code> field, and I have hooked the timer&#8217;s <code>Elapsed<\/code> event.  That creates one of those circular-reference structures.  I was aware of this, and didn&#8217;t think it would be a problem.  And indeed, by itself, it isn&#8217;t.<\/p>\n<p>But .NET Memory Profiler showed me that my <code>SharedConnection<\/code> object does still have a root path, which leads via the <code>Timer<\/code> to an object named <code>System.Threading._TimerCallback<\/code>.  I don&#8217;t know what that object is, it&#8217;s something internal to .NET and not documented anywhere.  But it&#8217;s reachable from a root, so I&#8217;m reachable from a root.  Damn!<\/p>\n<p>So, there you go.  If you have a <code>System.Timers.Timer<\/code> field, and you&#8217;ve hooked it&#8217;s <code>Elapsed<\/code> event, then you will never, ever get garbage-collected.<\/p>\n<p>Having realized that, I added this bit of code before the <code>GC.Collect()<\/code> call:<\/p>\n<pre><code>\r\nforeach( WeakReference weak_ref in ExistingConnections.Values ) {\r\n    SharedConnection connection = weak_ref.Target as SharedConnection;\r\n    if( connection != null ) {\r\n        connection.SuspendConnection();\r\n    }\r\n}\r\nGC.Collect();\r\n<\/code><\/pre>\n<p>The call to <code>connection.SuspendConnection()<\/code> will unhook the <code>Timer.Elapsed<\/code> (among other things), which should break the root path and finally allow the <code>SharedConnection<\/code> objects to get garbage-collected.<\/p>\n<p>But it still doesn&#8217;t work.  WTF?<\/p>\n<p>Taking another .NET Memory Profiler snapshot, I now see there are no root paths keeping the object alive.  So why didn&#8217;t the garbage collector collect it?<\/p>\n<h3>Phase 2<\/h3>\n<p>After much more head-scratching, and googling till my eyeballs bled, I finally had the idea to take another Memory Profiler snapshot right in there between the <code>foreach<\/code> loop and the <code>GC.Collect()<\/code>.  I added a 45 second delay in there to give me time to grab the snapshot.<\/p>\n<p>What this new snapshot showed was new root paths!  Paths that did not exist before the function was called, and would not exist again after.  But at that point, just before <code>GC.Collect()<\/code><\/p>\n<p>Here I&#8217;m going into conjecture, I don&#8217;t really understand C# enough to know for sure what&#8217;s happening.  But my guess is that it&#8217;s because of the strong reference I created inside the <code>foreach<\/code> loop.  I figured that once I leave the <code>foreach<\/code> loop, the strong references will go out-of-scope and be destroyed, because that&#8217;s what the scoping rules of C# say.  But I&#8217;m guessing that that&#8217;s not the entire story.<\/p>\n<p>Perhaps it&#8217;s actually like in unmanaged C\/C++: You can have local variables inside nested blocks, and their destructors run when you leave their enclosing scopes.  But the stack space for all of them is actually created the moment you call the function, and doesn&#8217;t get released until the end of the function.<\/p>\n<p>Perhaps it&#8217;s similar in C#: The nested hard-reference becomes inaccessible to C# source code after we leave the <code>foreach<\/code> block, but perhaps the variable itself continues to exist until the end of the function.  That would explain how it is that a hitherto unknown root path now exists when I call <code>GC.Collect()<\/code>.<\/p>\n<p>I finally fixed the problem by adding a <code>connection = null;<\/code> line at the end of the <code>foreach<\/code> block:<\/p>\n<pre><code>\r\nforeach( WeakReference weak_ref in ExistingConnections.Values ) {\r\n    SharedConnection connection = weak_ref.Target as SharedConnection;\r\n    if( connection != null ) {\r\n        connection.SuspendConnection();\r\n    }\r\n    connection = null;\r\n}\r\nGC.Collect();\r\n<\/code><\/pre>\n<p>It looks like a useless bit of ineffective code, but it makes all the difference.  Now the <code>GC.Collect()<\/code> finally collects my unused <code>SharedConnection<\/code> objects.<\/p>\n<p>What a struggle.  And I have to say, I&#8217;d never have figured it out without .NET Memory Profiler.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve spent a few days trying to track down a garbage collection problem in some code I&#8217;ve been working on.\u00a0 I finally figured it out. Along the way, I&#8217;ve learned more about .NET garbage collection than I ever wanted to know, and discovered a useful tool to help with these kinds of problems.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[34,31,35,32,33],"class_list":["post-471","post","type-post","status-publish","format-standard","hentry","category-tech","tag-net","tag-c","tag-clr","tag-garbage-collection","tag-garbage-collector"],"_links":{"self":[{"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/posts\/471"}],"collection":[{"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/comments?post=471"}],"version-history":[{"count":3,"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/posts\/471\/revisions"}],"predecessor-version":[{"id":744,"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/posts\/471\/revisions\/744"}],"wp:attachment":[{"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/media?parent=471"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/categories?post=471"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thegatesofdawn.ca\/wordpress\/wp-json\/wp\/v2\/tags?post=471"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}