Skip navigation

Mind the Gaps

Stop Memory Leak

asp:Feature

LANGUAGES: C#

ASP.NET VERSIONS: 2.0

 

Mind the Gaps

Stop Memory Leak

 

By Alvin Bruney

 

Arguably, there is no uglier beast than a memory leak. Applications that abuse memory can cripple resources. The disease can quickly spread across a server farm to wreak havoc on a Web site. Some .NET developers (in particular) have developed an unhealthy distrust of the mechanisms that govern resource allocation and de-allocation in a managed environment. For instance, code to close, dispose, null-out objects, force a garbage collection, wait for the finalizer to run, and force another collection is common place.

 

There is validity in the argument that this garbage collector paranoia is unhealthy, at best, because it rides high on the premise that the system is at fault. In my experience, applications that leak memory often do so because of faulty code. However, developers simply refuse to blame their code first. Mind you, in some instances the runtime is actually at fault. But these instances are few and far between, and well documented when they occur. Even in some extreme cases where the framework seems to be at fault, it often is not.

 

Upon Further Review

Let s examine one nasty piece of code that leaks memory. Allow me to set this up. This example builds a user control containing an image control that runs embedded in a browser. Code will then unload the user control and force a garbage collection to purge the control from the heap. We ll examine the heap using the SOS extensions for Windbg.exe to determine whether or not the control has been purged. The heap will show that a reference to the control still exists, even though the garbage collector ran and there are no references to the control occurring in code. This is about as nasty as they come. Figure 1 shows the simple control with three buttons and an image control.

 


Figure 1: The user control with an image displayed in a browser.

 

Now, let s quickly build the application. You ll need to see this in action to believe it:

1)     Download the project code to a directory on your hard drive (see end of article for download details).

2)     Using IIS Manager, create a virtual directory pointing to LeakTest.

3)     Make sure your virtual directory settings resemble Figure 2.

4)     Browse to the file titled MemoryLeakPOC.html.

 


Figure 2: IIS settings for the user control.

 

If you examine MemoryLeakPOC.html, you ll see that it contains three methods (loadControl, unloadControl, and performGarbageCollection) that map to the buttons shown in Figure 1. For this particular example, Code Access Security (CAS) policy does not need to be tweaked because it s sufficient to cause the application to work correctly. Parts of the html file are shown in Figure 3 for convenience.

 

// method used to load the control through an object tag

function loadControl()

{

 if (objectTag == null)

 {

   objectTag = document.body.appendChild(

     document.createElement("OBJECT"));

   objectTag.id = "LeakingUserControl";

   objectTag.width = 800;

   objectTag.height = 500;

       objectTag.classid="LeakTestControls/bin/Debug/

         LeakingUserControl.dll#LeakingUserControl.Leaker";

   var buttonTag = document.getElementById("loadControlButton");

   buttonTag.disabled = true;

   buttonTag = document.getElementById("unloadControlButton");

   buttonTag.disabled = false;

 }

}

//method used to unload the control

function unloadControl()

{

 if (objectTag != null)

 {

   document.body.removeChild(objectTag);

   objectTag.clearAttributes();

   objectTag.outerHTML = "";

   objectTag.outerText = "";

   objectTag = null;

   var buttonTag = document.getElementById("loadControlButton");

   buttonTag.disabled = false;

   buttonTag = document.getElementById("unloadControlButton");

   buttonTag.disabled = true;

 }

}

//start clean up

function performGarbageCollection()

{

 // Perform .NET garbage collection

 var garbageCollectorUserControl = document.getElementById(

   "garbageCollectorUserControl");

 garbageCollectorUserControl.PerformGarbageCollection();

 // Perform JavaScript garbage collection

 CollectGarbage();

}

Figure 3: Script functions.

 

Let s spend a moment talking about the loadControl method it essentially creates an object tag using script. The object is configured to source the control found in the debug folder. At run time, when this function is called, IIS will find and deliver the control to the client.

 

Notice the call to the script function performGarbageCollection it s responsible for triggering a garbage collection in the managed code. To make this possible, the GarbageCollectorControl.cs file (included in the download code) contains a class decorated with a few attributes that allow an instance of the class to be visible on the client. The code hooks all this up through the IGarbageCollectorControl interface.

 

I won t spend much time discussing the exact structure and details of the code because it isn t terribly important. The important point to understand is that the control will run in the browser and a garbage collection will be triggered from the client. The code uses a separate class defined in GarbageCollectorControl.cs, GarbageCollectorControl, to trigger the Garbage collection (it s designed that way to avoid re-using the control reference when the collection is triggered).

 

Examine the MyUserControl.cs file. This simple Windows control library contains a picturebox control and an image. When run, it produces the screen capture shown in Figure 1. The code-beside file for the user control displays a message box for each button that is clicked. The class constructor and destructor contain similar logic; pretty basic stuff.

 

The science behind the exercise is simple: When the control is loaded by clicking Load Control, a garbage collection will not occur because the control is active. Once the control is unloaded by clicking Unload Control, a garbage collection is free to occur. A collection can be triggered via the Perform Garbage Collection button. After a collection occurs, there should be no instances of the control in the managed heap. If we find an instance of the object in the managed heap, we can safely conclude that:

1)     There is a memory leak.

2)     The runtime is to blame.

 

Let s Test It

Launch the Web page and click the appropriate buttons to load, unload, and collect. At this point, there should be no objects on the managed heap because we unloaded the control and forced a garbage collection. Yet, if we examine the run-time heap with Windbg.exe, we clearly see an instance of the control on the heap (see Figure 4); it s clearly a leak, right?

 


Figure 4: Object 05eb052c leaking on the heap.

 

How can this be? Remember, by clicking the various buttons, we closed, disposed, nulled-out objects, forced a garbage collection, waited for the finalizer to run, and forced another collection. Clearly, after following this popular pattern, there shouldn t be any leaks. It s time to open a support incident to Microsoft Product Support! But wait if you care to dig deeper, you may realize that part of the problem is that we really haven t tested this thoroughly enough. As it turns out, we missed a crucial test case. Let s rewind and try again. Perform the same sequence of steps with the exception that you wait 10 minutes before clicking the garbage collection button. Now, examine the heap. Figure 5 confirms that the leak is gone!

 


Figure 5: No leaking objects present on the heap.

 

There must be some hocus pocus going on, right? If you play with the 10 minute time span a bit, you ll find that the magic number is 5 minutes. After this elapsed time, garbage collection cleans the object on the managed heap. If you try to garbage collect before a 5 minute time span, you ll have a leak (notice the quotes). So the problem wasn t in the code we wrote. Something is happening deep inside the runtime that is causing objects to hang around for 5 minutes before they are eligible for garbage collection.

 

At this point, we suspect that it isn t strictly a leak, but rather a configuration setting somewhere in the runtime. This is a safe conclusion to draw because of the exact 5 minute time span found earlier. Somehow, the configuration setting must be causing the user control to maintain a root. This will prevent garbage collection. It s a bit of educated speculation, but we re headed in the right direction.

 

When a managed user control is embedded in a Web form, data is marshaled across two application domains (app domains). The two app domains involved are the default Internet Explorer (IE) app domain and the app domain associated with the security zone of the .NET control. Because of marshaling, a reference for the managed user control is passed to the IE app domain. This remote reference uses .NET remoting to accomplish the marshaling. The marshaled reference has a lease life time associated with it set to 5 minutes by default. Garbage collections performed during this 5 minute interval will not reclaim the object because it has a valid root. When the time period expires, the resource can be garbage collected.

 

I did warn you that it was trick code. Unfortunately, you ll see examples of this type of undocumented behavior every once in a while. And even if your code didn t cause it, it s still your responsibility to find and fix it before the software ships. Code isn t always fair! Eventually, your experience will allow you to diagnose these issues correctly. Until then, your best bet is to first assume that your code is at fault. Then, design and build your test cases accordingly. Remember, test cases, if properly designed, can uncover a majority of thorny issues that exist in code.

 

There are valuable lessons in here. Memory issues are nasty and usually complicated because they often involve parts of the runtime with which we are not familiar. There s also a timing factor that is entwined in the collector algorithm that you need to be aware of if you intend to interfere with the collection mechanism. This particular example supports the recommendation of not interfering with the garbage collector. After all, you couldn t possibly know beforehand that remoting was at work behind the scenes. However, the framework is well aware of those references. You then cause problems for both yourself and the collector by triggering a collection. Instead, you should trust the managed environment to manage references and resolve memory pressures as it sees fit. Avoid calling the collector explicitly in code unless you have a very good reason to do so.

 

The C# source code accompanying this article is available for download.

 

Alvin Bruney is a consultant working with a large financial institution in North America. He recently wrote Programming Microsoft Office Excel 2007 Services, from Microsoft Press.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish