Friday, 20 April 2012

Garbage Collector .NET 4

The Basics

The garbage collector is an automatic memory manager. It provides the following benefits:

  • Enables us to develop the application without having to free memory.
  • Allocates objects efficiently on the managed heap.
  • Reclaims no longer used objects, clears their memory, and keeps the memory for future allocations.
  • Provides memory safety by making sure that an object cannot use the content of another object.

Garbage collection is non deterministic and occurs based on following condition:

  • When system has low physical memory.
  • The threshold of acceptable memory usage of allocated object has been exceeded on the managed heap. This threshold is continuously adjusted as the process runs.
  • The GC.Collect method is called.

The Heap

Simplified model of the managed heap

The rules for this simplified model are as follows:

  • All garbage-collectable objects are allocated from one contiguous range of address space.
  • The heap is divided into generations so that it is possible to eliminate most of the garbage by looking at only a small fraction of the heap.
  • Objects within a generation are all roughly the same age.
  • Higher-numbered generations indicate areas of the heap with older objects—those objects are much more likely to be stable.
  • The oldest objects are at the lowest addresses, while new objects are created at increasing addresses.
  • The allocation pointer for new objects marks the boundary between the used (allocated) and unused (free) areas of memory.
  • Periodically the heap is compacted by removing dead objects and sliding the live objects up toward the low-address end of the heap. This expands the unused area at the bottom of the diagram in which new objects are created.
  • The order of objects in memory remains the order in which they were created, for good locality.
  • There are never any gaps between objects in the heap.
  • Only some of the free space is committed. When necessary, more memory is acquired from the operating system in the reserved address range.

Generations

Garbage collection organizes the managed heap into three generations to handle long live and short live objects:

  • Generation 0. This is the youngest generation and contains short-lived objects. For example temporary variable. Garbage collection occurs most frequently in this generation.

Newly allocated objects are implicitly generation 0 collections, unless they are large objects (85000 bytes and larger), in which case they go on the large object heap in a generation 2 collection.

Most objects are reclaimed for garbage collection in generation 0 and do not survive to the next generation.

  • Generation 1. This generation contains short-lived objects and serves as a buffer between short-lived objects and long-lived objects.
  • Generation 2. This generation contains long-lived objects. An example of a long-lived object is an object in a server application that contains static data that is live for the duration of the process.

Objects that are not reclaimed in a garbage collection are promoted to the next generation. Objects that are not reclaimed in generation 0 garbage collection are promoted to generation 1; objects that are not reclaimed in a generation 1 garbage collection are promoted to generation 2; and objects that survive a generation 2 garbage collection remain in generation 2.

Finalization Queue and Thread

When the GC encounters object with Finalizers that are otherwise “dead” instead of reclaiming the space the objects are added to the Finalization Queue (it’s generation number remains the same).  An aptly named Finalization Thread then works through this queue running the finalizers and marking the objects as dead, ready for the next GC.

GC modes

Workstation vs Server

Workstation GC: The default GC mode and on a single-proc machine it’s the only option. A single heap collected at normal priority.

Server GC: Only available on multi-proc machines it creates one GC heap (and thread) per processor, which are collected in parallel at highest priority.

<configuration>
<runtime>
<gcServer enabled=”true” />
</runtime>
</configuration>

Before .NET 4:  Concurrent (Workstation only)


A basic (non concurrent) GC uses a "stop-the-world" strategy. When the GC must run, all applicative (managed) threads stop. The last stopping thread runs the GC, and unblocks all the other threads when it has finished.


Concurrent GC is used on a multi-proc machine to perform full collections (generation 2) concurrently with the running program, therefore minimizing the pause time. This mode is particularly useful for applications with graphical user interfaces or applications where responsiveness is essential.


Remember, Concurrent GC is only used with generation 2; generations 0 and 1 are always non-concurrent because they finish very fast.


Enabled by default here is how to disable this behaviour:

<configuration>
<runtime>
<gcConcurrent enabled=”false” />
</runtime>
</configuration>

Introduced in .NET 4:  Background [and Foreground] (Workstation only)


Starting with the .NET Framework version 4, background garbage collection replaces concurrent garbage collection. 


In background garbage collection, ephemeral generations (0 and 1) are collected as needed while the collection of generation 2 is in progress. There is no setting for background garbage collection; it is automatically enabled with concurrent garbage collection. As with concurrent garbage collection, background garbage collection is performed on a dedicated thread and is applicable only to generation 2 collections.



So as to allow the background and foreground threads to play nicely with each other, a staging area is found as the alive (blue) objects move from up the generations.



Background GC will be introduced for the Server in .NET 4.5:


http://channel9.msdn.com/posts/Maoni-Stephens-CLR-45-Server-Background-GC


Notifications


Warning:  This code will cause you to run out of memory.

using System;
using System.Collections.Generic;
using System.Threading;

class Program
{
// Variable for continual checking in the
// While loop in the WaitForFullGCProc method.
static bool checkForNotify = false;

// Variable for suspending work (such servicing allocated server requests)
// after a notification is received and then
// resuming allocation after inducing a garbage collection.
static bool bAllocate = false;

// Variable for ending the example.
static bool finalExit = false;

// Collection for objects that simulate the server request workload.
static List<byte[]> load = new List<byte[]>();


public static void Main(string[] args)
{
try
{
// Register for a notification.
GC.RegisterForFullGCNotification(10, 10);
Console.WriteLine("Registered for GC notification.");

checkForNotify = true;
bAllocate = true;

// Start a thread using WaitForFullGCProc.
Thread thWaitForFullGC = new Thread(new ThreadStart(WaitForFullGCProc));
thWaitForFullGC.Start();

// While the thread is checking for notifications in
// WaitForFullGCProc, create objects to simulate a server workload.
try
{
int lastCollCount = 0;
int newCollCount = 0;

while (true)
{
if (bAllocate)
{
load.Add(new byte[1000]);
newCollCount = GC.CollectionCount(2);
if (newCollCount != lastCollCount)
{
// Show collection count when it increases:
Console.WriteLine(
"Gen 2 collection count: {0}",
GC.CollectionCount(2).ToString());
lastCollCount = newCollCount;
}

// For ending the example (arbitrary).
if (newCollCount == 500)
{
finalExit = true;
checkForNotify = false;
break;
}
}
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Out of memory.");
}

finalExit = true;
checkForNotify = false;
GC.CancelFullGCNotification();
}
catch (InvalidOperationException invalidOp)
{
Console.WriteLine(
"GC Notifications are not supported while concurrent GC is enabled.\n"
+ invalidOp.Message);
}
}

public static void OnFullGCApproachNotify()
{
Console.WriteLine("Redirecting requests.");

// Method that tells the request queuing
// server to not direct requests to this server.
RedirectRequests();

// Method that provides time to
// finish processing pending requests.
FinishExistingRequests();

// This is a good time to induce a GC collection
// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC.Collect();
Console.WriteLine("Induced a collection.");
}


public static void OnFullGCCompleteEndNotify()
{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console.WriteLine("Accepting requests again.");
}

public static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC.WaitForFullGCApproach();
if (s == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notifiction raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period is specified for
// WaitForFullGCApproach(Timeout) or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.");
break;
}

// Check for a notification of a completed collection.
s = GC.WaitForFullGCComplete();
if (s == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notifiction raised.");
OnFullGCCompleteEndNotify();
}
else if (s == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console.WriteLine("GC Notification not applicable.");
break;
}
}

Thread.Sleep(500);
// FinalExit is set to true right before the main thread cancelled notification.
if (finalExit)
break;
}
}

private static void RedirectRequests()
{
// Code that sends requests to other servers.

// Suspend work.
bAllocate = false;
}

private static void FinishExistingRequests()
{
// Code that waits a period of time for pending requests to finish.

// Clear the simulated workload.
load.Clear();
}

private static void AcceptRequests()
{
// Code that resumes processing requests on this server.

// Resume work.
bAllocate = true;
}
}

Performance Counters


PerfMon lets you track the Garbage Collectors activity:


image


image


image


Profiler


http://memprofiler.com/

2 comments: