Introdution
In this article, I will start by talking about the .NET’s garbage collector. Then, I will give you a strong reason for using the code-behind technique of ASP .NET.
Automatic Memory Management
One of the CLR features is the automatic memory management mechanism. With automatic memory management, the developer doesn’t need to track memory usage and know how or when to free memory.
To create an object you use the new operator and… that’s it. When you don’t need to use that object, you simply stop using it. Using other words, you stop using the reference to that object.
But since you have no control on how and when the memory of the object will be reclaimed:
- How do we know if there is enough memory for creating that object?
- How about the object’s unmanaged resources?
- How do we know if the object is still alive?
How do we know if there is enough memory for creating that object?
The advantages of creating objects in the managed heap exist because the managed heap makes an important assumption: the address space and storage infinite. It can make this assumption because of the existence of an important CLR mechanism called the Garbage Collector.
Introducing the Garbage Collector (GC)
The CLR has a mechanism called the Garbage Collector to manage the memory in the managed heap.
When there’s no memory left for creating a new object in the managed heap, the gc will stop the program’s execution and will start looking for objects that aren’t referenced by roots(*1). A root is a storage location containing a memory pointer to a reference type object. If the object isn’t referenced by any root that object will be considered garbage and it’s memory will be freed.
How about the object’s unmanaged resources?
Sometimes, stop referecing an object and let the garbage collector reclaim it’s memory isn’t enough. Consider if the object wraps some unmanaged resources (e.g: files, database connections). Before having it’s memory freed it should release it’s unmanaged resources.
For an object have the capabilty to deterministically dispose an objct, it must implement the dispose pattern.
To implement the dispose pattern the type must implement the IDisposable interface defined in the FCL. It should define a public, parameterless Dispose method that can be explicitly called to release any unmanaged resources wrapped by the object. However, the memory is still being managed by the GC, i.e, the memory will only be freed in the next garbage collection.
How do we know if the object is still alive?
In fact, an object isn’t garanteed to live till the end of the scope of a method.
Yes, it’s true.
Let me show you one of the wonders of .NET’s CLR.
The JIT compiler and the GC
The JIT compiler translates the intermediate language (IL) into native code.
Consider this code:
void SomeMethod()
{
SomeHeavyMemObject sho = new SomeHeavyMemObject();
...
sho = null;
}
When the JIT compiler compiles the IL for a method into native code, it checks to see if the assembly defining the method was compiled without optimizations and if the process is being executed under a debuffer.
If both are true, the JIT compiler will artificially act like the SomeHeavyMemObject must until the end of the method. The JIT compiler does this to make possible JIT debugging. Imagine trying to debug a program just to find out that the variable that you wanted to see was garbage collected!
Else, the JIT works as an optimizing compiler. So as soon as you stop to reference a variable, that variable is considered garbage and it’s memory will be reclaimed at the next garbage collection. In fact, setting SomeHeavyMemObject to null is the same as not reference it at all.
Now let’s see one more reason for using the ASP .NET’s code-behind technique.
Why you should use ASP .NET Code-Behind
Imagine that you have an object in your SamplePage class called SomeHeavyMemObject. Imagine that you don’t care about using the ASP.NET’s code-behing techique and you use the following code:
public class _Default : System.Web.UI.Page
{
protected SomeHeavyMemObject sho = new SomeHeavyMemObject();
private void Page_Load(object sender, System.EventArgs e)
{
}
}
In the .aspx.cs
<%=sho.GiveSomeHtml()%>
In the .aspx
Now remenber that ASP.NET pages are always compiled into .NET classes housed within assemblies. This class includes all of the server-side code and the static HTML, so once a page is accessed for the first time subsequent rendering of that page is serviced by executing compiled code. That particular line of code is going to be placed inside the Render method of our page derived class.
That is a major disadvantage.
Think about it…
Even if you compile your page with optimizations, the variable SomeHeavyMemObject must live at least until it’s GiveSomeHtml method is called inside the Render method. Only then the GC could collect it’s memory.
It’s even worse, if SomeHeavyMemObject wraps some unmanaged resource. If it was true, you could only call the Dispose method in the UnLoad event of the Page, making the object live until that point.
Now, if you choosed to use the code-behind technique:
public class _Default : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Literal literal;
private void Page_Load(object sender, System.EventArgs e)
{
using(SomeHeavyMemObject sho = new SomeHeavyMemObject())
{
literal.Text = sho.GiveSomeHtml();
}
}
}
In the .aspx.cs
...
<asp:Literal runat="server" id="literal" name="literal"/>
...
In the .aspx
Using this technique the SomeHeavyMemObject only lives the exact time that it needs and it’s unmanaged resources will be collected by the end of the Load method. Also, if there’s a garbage collection SomeHeavyMemObject will be considered garbage and it’s memory will be freed.
Conclusion
We’ve seen how the GC works and how the JIT compiler knows when you stop using your reference type variables. Using that knowledge we’ve seen how using the code-behind technique can optimize memory usage.
*1 - This is a simplification. The CLR’s Garbage Collector is a generational collector, that is, every time a new reference type object is created it will go to the Generation 0 (Gen 0). When Gen 0 is full, the program execution will stop and a collection will take place. The objects that survived the collection will be promoted to the Gen 1, and so on. The CLR’s Garbage Collector has up to 3 generations.