Get to Know .NET 4.0's CLR

Explore enhancements in CLR 4.0 that improve application performance, memory and thread management, and security

The Common Language Runtime (CLR) is at the core of the .NET Framework development environment. It is the part of .NET that is ultimately responsible for memory management, thread management, exception handling, and security. The CLR has not had any significant changes since 2005 when .NET Framework 2.0 was shipped. All the version 3 frameworks shipped in the meantime (.NET 3.0, 3.5, and 3.5 SP1) didn't modify the CLR in a significant way. But CLR 4.0 adds some significant enhancements in every area of the CLR.

Side-by-Side Execution
A key starting point in understanding the improvements in CLR 4.0 is side-by-side execution. Folks that remember the pain of migrating from 1.1 to 2.0 will rejoice: Your applications are not forced to roll forward to the newest framework any more. Applications built in 2.0 or newer versions of the framework will continue to run on that version of the framework and CLR without incident, even within the same process.

This becomes hugely important when you're using third-party libraries and tools in your application. You can move your own code up to 4.0, but the components can still be running on 2.0. This feature alone mitigates a lot of fear of upgrading to the latest and greatest; you don't have to make sure every bit of your application is migrated to 4.0 all at once.

It also has significant impact if you're still using COM components. In previous versions of the CLR, the COM component had to run in the same version of the framework as the application itself. With the 4.0 CLR, COM components can run under their native framework version (typically version 2), or you can tell CLR 4.0 to support 2.0 behavior if the 2.0 framework isn't available by modifying the startup tag in the configuration section of the application's configuration file, like this:

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" />
  </startup>
</configuration>


Also, in the application configuration file you can specify preferred frameworks in sequence. So if you prefer your application to run on .NET 2.0 SP1, but if that version isn't available, to run on .NET 4.0, the file would look like this:

 

<configuration>
  <startup >
    <supportedRuntime version="v2.0.5727" />
    <supportedRuntime version="v4.0" />
  </startup>
</configuration>


For many situations, you need to make no changes to your application to move to the 4.0 CLR and framework. The various components of your application will use the versions of the framework that it requires.

Garbage Collection
Most developers take the memory management abilities of the CLR entirely for granted. You never need to think about how variables get allocated in memory; you just declare them. And you don't worry much about cleaning them up—it just happens. The CLR makes memory management almost entirely transparent.

The only aspect of CLR memory management that developers are aware of is garbage collection. In the CLR, variables are allocated quickly, but freed up slowly. Deallocating a variable, typically by letting it fall out of scope, marks the variable as deleted but does not return the memory to the heap for use—that happens only when garbage collection runs. For the most part, garbage collection is invisible to the application. In fact, it is strongly recommended that you don't mess with garbage collection, such as forcing a collection to run with a GC.Collect statement.

The only time most developers think about garbage collection is when it affects their application. Typically this only occurs in two scenarios: client application freezes and page-processing delays on a web server. To understand how this happens, you need to understand a bit more about how garbage collection works.

Memory management in the CLR is broken into three generations made up of groups of memory segments. The first two are Gen 0 and Gen 1, called ephemeral generations since they are generally very short-lived. When an object is first created, it lives in Gen 0 until garbage collection runs. If by the time garbage collection runs the object is dead, that is, already deallocated and finalized, the memory that was allocated to that object is freed for use. If the object is still in use, it gets promoted to Gen 1. In some cases an object is deallocated but not finalized; that is, the garbage collector will fire the finalizer of the object but still ships the object over to Gen 1. The object will be cleaned up on the next garbage collection.

If when garbage collection is run on a Gen 1 segment there are objects still in use, those objects are moved to Gen 2. In reality, "move" is a misnomer—the garbage collector does its best not to move memory around, since doing so is a slow process. The garbage collector will compact down live objects to fill in the gaps in memory left by dead objects. But ultimately a segment normally never moves, it just gets marked as a different generation.



Gen 2 segments are where the pain lives for garbage collection. Objects in Gen 2 tend to be long-lived and in applications where garbage collection becomes a problem—inevitably it is a large Gen 2 store that's the problem. Normally, garbage collection is a blocking process. This isn't a big deal for Gen 0 and Gen 1 segments because the garbage collection runs so quickly. But because Gen 2 can get so large, garbage collection takes longer, causing pauses in the UI of an application or forcing a web server to delay processing of web pages.

Before CLR 4.0, garbage collection on Gen 2 worked in concurrent mode. This meant that memory could still be allocated in Gen 0 while a Gen 2 garbage collection was running. So in effect, execution of your application is not interrupted by a Gen 2 garbage collection, at least for a little while. Where things break down is when it's time for a Gen 0 or Gen 1 garbage collection and the Gen 2 garbage collection is still running—then everything stops. The ephemeral generations cannot run garbage collection while the Gen 2 garbage collection is running.

In CLR 4.0, background garbage collection has been introduced, replacing concurrent garbage collection. Background garbage collection allows the ephemeral generations to run simultaneously with the Gen 2 garbage collection. Actually, it's not truly simultaneous; what has been added is that while a Gen 2 garbage collection is running, it checks to see whether the Gen 0 or Gen 1 collection needs to run and pauses its processing to let the much faster ephemeral generation garbage collection finish. This should keep your application working as long as your threads aren't dependent on memory objects in the Gen 2 segment.

One caveat is that this new background garbage collection is available only in the workstation mode of garbage collection. Server mode does not have this capability. Workstation and server mode garbage collection have nothing to do with the OS per se; they are modes that depend on multiple core machines. Server mode garbage collection will use many threads across many cores to do garbage collection very rapidly, although in the concurrent and blocking way.

The workstation mode of garbage collection is the default mode of the CLR unless you're running in the CLR in ASP.NET or within SQL Server—then you default to server mode. You can also set your application to run in server mode using the <gcServer> tag in the application configuration file. Also, a single CPU machine always runs the CLR in workstation mode.

Background garbage collection should address your applications having UI hauls due to garbage collection, but the workstation mode restriction means it won't help much for ASP.NET. In reality, background collection doesn't make sense when there are huge numbers of discrete processes all running against the same set of heaps, which is the normal condition of a busy ASP.NET site.

But there's another feature in CLR 4.0 that a developer very concerned about ASP.NET garbage collection performance can take advantage of: garbage collection notifications. There's a "death spiral" that an ASP.NET server can go into when it's running short of memory and under extreme load. As memory gets low, the Gen 2 garbage collection runs to try and free up memory. Often threads are blocked because they depend on objects in Gen 2, like in-process session objects (you're not still using in-process session objects are you?). While the Gen 2 garbage collection runs, the server ends up with all threads blocked, resulting in request queues growing. When the garbage collection finishes, the server is hammered by all the requests that are backed up in the queue. That backlog runs the server out of memory again, causing another Gen 2 garbage collection. With each iteration the queues get larger, until the worker process recycles or the server crashes.

In CLR 4.0 you can subscribe to an event called RegisterForFullGCNotification, which notifies you when a Gen 2 garbage collection will run. When this event occurs, you could set a flag that would cause all web pages to fire a Server.Transfer to a different server. Alternatively, you could send a command to a load balancer to effectively pull the machine having the garbage collection out of the server farm.

While the redirect is running, monitor for the WaitForFullGCComplete to know when to stop doing redirection. This technique won't solve the workload already on your server, but it will stop additional traffic from making the situation worse. Note also that garbage collection notifications really only work in server garbage collection mode. You can make it work in workstation mode if you disable concurrent garbage collection.

Thread Management
Most of the changes in thread management for CLR 4.0 have to do with the reality of parallel execution. Multicore computers are the norm today, and programming has to adjust to take advantage of that. More cores naturally mean more threads in an application.



Microsoft has added a number of libraries to take advantage of parallelism, including the Parallel Task Library, which is a topic for another article. (For an in-depth look at parallelism, see "Parallel Computing with .NET 4.") The changes to the CLR 4.0 for thread management focus on dealing with many more threads in an application. A key change is the introduction of local queue for each thread in a process.

Normally there's a single global work queue for all threads in a given application. As the number of threads grows, this queue would get very busy and contentious. In CLR 4.0, every thread gets its own local queue as well, largely to mitigate contention for work. The only problem with multiple queues is that you'll get to a situation where there are several items in one thread's local queue, but nothing in another thread's local queue or the global queue. CLR 4.0 has a feature called work stealing, where a thread with nothing to do can "steal" work from another local queue.

Like most features of the CLR, these new thread management capabilities are largely invisible to the average developer. But without them, the new styles of parallel development would be significantly impaired.

Security
Until CLR 4.0, security in .NET meant Code Access Security (CAS). CAS still exists in CLR 4, but it has changed significantly. CAS breaks down into three parts: policy, permissions, and enforcement. In the CLR 4 CAS policy has been deprecated. Policy decisions are left up to the host of the application. CAS policy is where the pain of CAS largely lived: Caspol.exe was never easy to use, deploying policies is difficult, and the differences in behavior between managed and native code often confused things.

What this means to the average application is that if a user starts the application directly (double-clicking an icon, for example), that application runs in full trust mode. After all, the act of the user starting the application is an implication of trust—you trusted it enough to start it. So why shouldn't it just work?

That being said, the permissions and enforcement models of CAS are alive and well in CLR 4.0. Medium trust continues to work as per normal for security scenarios like ClickOnce and ASP.NET. Microsoft has also added the SecAnnotate tool to help expose libraries in a partial trust sandbox.

Diagnostics and Performance
One of the challenges developers have had in the past is determining whether a particular application domain was affecting other application domains. OS tools like Windows Task Manager monitor only at the process level. With CLR 4.0, processor and memory usage is measured per application domain.

For the most part, the improvements in diagnostics come from CLR 4.0 adding additional events in Event Tracing for Windows (ETW). Specifically, there are now Application Domain Resource Monitoring events for memory and thread allocation at the application domain level. If you want to start doing detailed tracing of ETW, you should download the Windows Performance Analysis Tools.

There are many more features in CLR 4.0, particularly core language elements expanding support for generics like System.Collections.Generic.SortedSet(T). There's also covariance and contravariance, a topic that could take a whole article to explain. CLR 4.0 also includes new data types, including System.Numerics.BigInteger (an arbitrary-precision integer), System.Numeric.Complex (for arithmetic and trigonometric operations) and a System.Tuple class for handling structured data.

Moving Forward
The latest version of the CLR is a big step forward, not only in dealing with past issues that made development challenging in some cases, but also setting the stage for new development techniques in the future. The addition of the Dynamic Language Runtime to support Ruby and Python, plus the addition of the F# language indicate a few different directions that Microsoft will be taking development in the future. Make sure you take some time to explore MSDN and other articles in DevProConnections magazine about these new features in the .NET 4.0 Framework.

Richard Campbell ([email protected]) is a co-founder of Strangeloop Networks. He has more than 30 years of high-tech experience and is both a Microsoft Regional Director and Microsoft MVP. In addition to speaking at conferences around the world, Richard is co-host of the .NET Rocks! (www.dotnetrocks.com) and host of RunAs Radio (www.runasradio.com).

 

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