Saturday, October 11, 2008

Memory Allocation - Generation GC

Since it is generational GC, in the marking phase it does not go through the entire heap.

It makes the following assumptions

1. The newer an object is, the shorter its lifetime will be.
2. The older an object is, the longer its lifetime will be.
3. Collecting a portion of the heap is faster than collecting the whole heap.

The picture shows how it works. A few things to remember

1. Generation 0 is empty immediately after a garbage collection and is where
new objects will be allocated.

2. Any objects that were in generation 0 that survived the garbage collection would be now in generation 1 and so on.

3. The objects in a generation are examined only when generation reaches its budget, which usually requires several garbage collections of generation 0. When the CLR initializes, it selects budgets for all three generations. The budget for generation 0 is about 256 KB, and the budget for generation 1 is about 2 MB. The budget for generation 2 is around 10 MB.

4. The larger the budget, the less frequently a garbage collection will occur.

5. The managed heap supports only three generations: generation 0, generation 1, and generation 2; there is no generation 3.

6. Garbage collector dynamically modifies generation 0's budget after every collection.

Memory Allocation - Details of Garbage Collection

Every type identifies some resource available for the program to use. The steps to allocate memory to resource are

1. Allocate memory for that type by calling the the new operator.

2. Initialize the memory to set the initial state of the resource and to make the resource usable. The type's instance constructor is responsible for setting this initial state.

3. Use the resource by accessing the type's members (repeating as necessary).

4. Tear down the state of a resource to clean up.

5. Free the memory. The garbage collector is solely responsible for this step.

But the garbage collector knows nothing about the resource represented by the type in memory, which means that a garbage collector can't know how to perform Step 4 in the preceding list. So how is step 4 performed... The developer writes this code in the Finalize, Dispose, and Close methods.

Step 4 can be skipped for managed resources for example String. But for a type that wraps a native resource such as a file, a database connection, a socket, a mutex, a bitmap, an icon, and so on, always requires the execution of some cleanup code when the object is about to have its memory reclaimed.

Garbage collection algorithm

1. Marking phase

The garbage collector marks all of the reachable objects.

2. Compaction phase

The garbage collector compacts the memory, squeezing out the holes left by the
unreachable objects.

But the GC is a generational collector. So in the marking phase, instead of marking the whole heap it just focusses on generation (or a portion of heap).

Finalization - Releasing Native Resources

Any type that wraps a native resource, such as a file, network connection, socket, mutex, or other type, must support finalization. Basically, the type implements a method named Finalize. When the garbage collector determines that an object is garbage, it calls the object's Finalize method (if it exists).

When an application creates a new object, the new operator allocates the memory from the heap. If the object's type defines a Finalize method, a pointer to the object is placed on the finalization list just before the type's instance constructor is called. The finalization list is an internal data structure controlled by the garbage collector. Each entry in the list points to an object that should be finalised.



A. First Pass

When a garbage collection occurs, objects B, E, G, H, I, and J are determined to be garbage. The garbage collector scans the finalization list looking for pointers to these objects. When a pointer is found, the pointer is removed from the finalization list and appended to the freachable queue which is another of the garbage collector's internal data structures. Each pointer in the freachable queue identifies an object that is ready to have its Finalize method called. After the collection, the managed heap looks like



A special high-priority CLR thread is dedicated to calling Finalize methods. Because of the way this thread works, you shouldn't execute any code in a Finalize method that makes any assumptions about the thread that's executing the code. So if an object is in the freachable queue, the object is reachable and is not garbage. Then when the garbage collector moves an object's reference from the finalization list to the freachable queue, the object is no longer considered garbage and its memory can't be reclaimed.

B. Second Pass

The next time the garbage collector is invoked, it will see that the finalized objects are truly garbage because the application's roots don't point to it and the freachable queue no longer points to it either. The memory for the object is simply reclaimed.



Questions

1. Why can't C# support destructors ?

The CLR doesn't support deterministic destruction, which makes it difficult for C# to
provide this mechanism.

Saturday, October 4, 2008

Threads - Thread Pool

When a thread is created:

1. A kernel object is allocated and initialized
2. The thread's stack memory is allocated and initialized,
3. Windows sends every DLL in the process a DLL_THREAD_ATTACH notification, causing pages from disk to be faulted into memory so that code can execute.

When a thread dies:

1. Every DLL is sent a DLL_THREAD_DETACH notification
2. The thread's stack memory is freed
3. The kernel object is freed (if its usage count goes to 0).

So, there is a lot of overhead associated with creating and destroying a thread that has nothing to do with the work that the thread was created to perform in the first place.

Thread Pool can come to rescue here. There is one thread pool per process; this thread pool is shared by all AppDomains in the process.

ThreadPool class is a static class. The CLR's thread pool will automatically create a thread, if necessary, and reuse an exiting thread if possible. Also, this thread is not immediately destroyed; it goes back into the thread pool so that it is ready to handle any other work items in the queue.

1. All the threads in ThreadPool are background threads.
2. All the threads run with normal priority and should not be changed.
3. The threads from ThreadPool should not be aborted.
4. Internally, the thread pool categorizes its threads as either worker threads or I/O threads.
a. Worker threads are used when your application asks the thread pool to perform an asynchronous compute-bound operation.
b. I/O threads are used to notify your code when an asynchronous I/O-bound operation has completed.
5. When using ThreadPool's QueueUserWorkItem method to queue an asynchronous operation, the CLR offers no built-in way for you to determine when the operation has completed.
6. If a thread pool thread has been idle for approximately 2 minutes, the thread wakes itself up and kills itself in order to free up resources.

Number of threads in thread pool

A thread pool should never place an upper limit on the number of threads in the pool because
starvation or deadlock might occur.

With version 2.0 of the CLR, the maximum number of worker threads default to 25 per CPU in the machine2 and the maximum number of I/O threads defaults to 1000.

If you think that your application needs more than 25 threads per CPU, there is something seriously wrong with the architecture of your application and the way that it's using threads.

Uses of Thread Pool

A thread pool can offer performance advantage. It offers the following capabilities

1. Calling a method asynchronously
2. Calling a method at a timed interval
3. Calling a method when a single kernel object is signaled
4. Calling a method when an asynchronous I/O request completes

Calling a Method Asynchronously

To queue a task for the thread pool, use the following methods. Using QueueUserWorkItem might make your application more efficient because you won't be creating and destroying threads for every single client request.

public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);
public static Boolean QueueUserWorkItem(WaitCallback wc);

public delegate void WaitCallback(Object state);

Calling a Method at Timed Intervals

The System.Threading namespace defines the Timer class. When you construct an instance of the Timer class, you are telling the thread pool that you want a method of yours called back at a particular time in the future.

Internally, the CLR has just one thread that it uses for all Timer objects.

If your callback method takes a long time to execute, the timer could go off again. This would cause multiple thread pool threads to be executing your callback method simultaneously. Watch out for this; if your method accesses any shared data, you will probably need to add some thread synchronization locks to prevent the data from becoming corrupted.

Calling a Method When a Single Kernel Object Becomes Signaled

Use the Register and Unregister WaitHandle methods.

When not to use Thread Pool thread

1. If I wanted the thread to run at a special priority (all thread pool
threads run at normal priority, and you should not alter a thread pool thread's priority).


2. You want the thread a foreground thread (all thread pool threads are background threads), thereby preventing the application from dying until my thread has completed its task.

3. I'd also use a dedicated thread if the compute-bound task were extremely long running; this way, I would not be taxing the thread pool's logic as it tries to figure out whether to create an additional thread.

4. Finally, I'd use a dedicated thread if I wanted to start a thread and possibly abort it prematurely by calling Thread's Abort method