My colleague Doug Ware of Magenic found another leak in the .NET framework this week. It seems that every time a MenuItem is added to a ContextMenu in .NET WinForms, it adds a reference to the MenuItem in an internal static hashtable. This may not be “wrong” if you only setup your context menu one time, but if you do things dynamically it means that all the old context menuitems are still technically reachable. To make things worse, the menuitems contain a reference to their parent, which is often a form, which might have references to large objects like datasets or images. Of course, once you find the problem and know what to look for, you can always find other mentions of it already on the net, along with the work-arounds. Its again frustrating though that something that's potentially so common, with a big impact too, which has been reported to Microsoft for quite some time apparently, is still not noted in any official Microsoft list that I can find. So if you are using ContextMenus -- beware -- and get the .NET Memory Profiler to help you find your own similar leaks. By the way, Doug is really getting good at this -- I found a leak in our own code today that I probably wouldn't have found before helping him recently -- although he still found the solution before I could.
I've received a couple of emails lately due to my postings where I've apparently made some people worried about .NET. I want to make it very clear that I am very very impressed with the .NET Garbage Collector -- there is no need to be “worried”. I've built enough systems, both before .NET and with .NET, to have experienced the blessings of the .NET GC -- they are real. I have yet to build an application in .NET that leaked any memory in the old sense that it was not even reclaimable after the app was terminated, except in cases where a 3rd party control had leaks due to unmanaged code. Yes, there are a few cases I've seen where .NET will leak memory, but only in the newer sense that your application's footprint keeps growing while it is still running -- it was always freed upon termination which means the GC did work. Its also possible to have these types of leaks in your own code -- we found one ourselves today where we had an object chain that included an object that was included in a static collection. I've also been very frustrated that the experts, including Microsoft people, have made simplistic statements that are just wrong in some cases -- one person that emailed me told me that the MS consultants at his site assured him that I was wrong about setting objects to null. I'm also very frustrated that we actually have to keep refinding the leaks in the framework that have been found before, but which Microsoft still does not list anywhere on their own site -- not even in the partner level KB that I have access to as an MVP. But none of this means that the .NET Garbage Collector is flawed -- its not -- it works very well, although it makes some assumptions that we developers need to understand so that we can work with it, or even help it in some cases. Its also true that GC in general is lazy, so there are probably some systems that should not be based on such systems, although those are probably few and far between (although maybe I have one).
Its very common practice to not call Dispose on a lot of .NET objects, like DataSets or SqlCommands for instance. There's even been several discussion on these blogs about the best practices in some of these cases. The problem is that many of us “know” that calling Dispose on some objects actually does nothing underneath -- it simply exists due to an inheritance chain that included the IDisposable interface. So many experts (myself included) have gotten in the habit of writing the most “efficient” code, which often means the fewest lines necessary. So why call Dispose if we know it does not do anything? As my colleague Doug Ware pointed out to me, this assumes some internal knowledge that should not be relied upon and which technically could even change in the future. Instead, we should remember that the IDisposable pattern was implemented for a reason in general, and so we should follow it or risk being in error at some point. In other words, what's the harm in writing this extra line of code that “might” have significant reasons for existing.
.NET GC Myth #2 -- The GC Frees Memory
.NET Garbage Collection Myth #1:
Newbies to .NET often criticize its memory management and its garbage collector,
but the criticisms are typically based on a lack of understanding and nothing more.
For instance, many have observed that trivial .NET applications allocate about 20MB,
but the incorrect assumption that often follows is that the .NET runtime needs it.
Instead, we know that allocating memory is time-consuming, so its generally better
when there is lots of memory to just go ahead and allocate a big chunk all at once.
This means that our .NET applications can actually perform better in many cases,
since they don't have to be constantly allocating and deallocating needed memory.
Similarly, the garbage collector is often criticized by newbies to .NET unfairly,
because the implication of GC is that memory is not released as quickly as possible.
While this is true, its generally an acceptable trade-off with plenty of memory,
since garbage collection frees us from having to worry about memory ourselves.
That's right, we know that GC means we easily end up with more reliable systems,
with fewer memory leaks without investing tons of time managing memory manually.
We finally found another leak in our Citrix WinForms app. Early tests show this one to be huge, so we are optimistic that this will clear up most of our problems. Apparently the XmlSerializer creates dynamic assemblies, but they only get reused if you stick to the simplist constructors. We are using one of the more complex constructors, so we were essentially leaking dynamic assemblies since .net provides no way to remove assemblies from an AppDomain. I can't take credit for finding the problem, one of my colleagues here did that, but I found the solution -- just create it once and reuse it. Its also interesting to note that now that I know what to look for, its easy to find other people that have ran into this before, like Joseph Cooney and Scott Hanselman.
I discovered today that the _global_ .net memory performance counters simply do NOT work. They don't tell you anything at all about the sum total of all your .net processes. They instead only report the last sample that was collected, regardless of the process. This is not at all documented as far as I can tell. In fact, the MSDN docs specifically go out of their way to say this is the behavior of only the 3 counters that track the total number of collections, implying that the others work fine. I was heavily using the counters about the total number of bytes that .net had in its various heaps, and this is the correct usage according to the books and articles I've seen. But they clearly don't work since you can set counters for each individual process and compare them to the global counter for yourself. Its very frustrating to find out that all of my performance tests have been for nothing, since they assumed that the performance counters were reliable. It also begs the question that I'm still having problems actually tracking down in Citrix (or Terminal Server) -- does the .net garbage collector understand that there are other processes also running at the same time? Everyone without Citrix experience trys to tell me that of course the .net gc works right in Citrix, but it seems that the few other people that are trying keep having the same questions. And now its apparent that the global .net memory performance counters are unaware of multiple processes, so . . . ? By the way, someone from Microsoft noted that the .net gc listens to the low memory notification event to know when it needs to work. But guess what -- the default setting for this is that your memory is low when you only have 32MB left on a 4GB server! There's also nothing I can find anywhere that tells you how to change this default setting to something more reasonable. That number sounds too low in any setting, but imagine a Citrix server with many users all having processes open -- when there's only 32MB left it will be far too late to do anything without severely impacting performance.
Ryan Dunn and I have been having a dialog on the ASP.NET Forums about my recent article on Mixing Forms and Windows Security in ASP.NET. He has another technique that attempts to do something similar here on GotDotNet and he very much disagrees that my solution is sufficient. Basically, my solution only demos how to combine Forms and Windows Authentication to automatically capture an Intranet user's name. His method instead combines Forms and Windows Authorization by creating a WindowsPrincipal that roles can be checked against. I apologize if someone thinks I've misled them since my article did not go all the way and illustrate the combined Authorization also, so I'm attaching the small amount of code, based on Ryan's work, that will create the WindowsPrincipal and complete the example.