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.