1
2
The developer has to manage the memory for applications written in languages prior to .NET C and C++ using the respective APIs. If this is not done, problem of memory leakage occurs. If the memory is full, exception may occur. Also sometimes problem of dangling reference occurs when there is attempt is made to refer to the memory that is already freed. In COM applications, memory is managed using the reference counting mechanism. Every object maintains the reference count internally. The count gets automatically incremented when any new reference to the same object is created. When a reference to an object goes out of scope, the counter is decremented. Memory is released when the count becomes zero. .NET developers do not have to bother about the same. Garbage collector component of CLR performs this task. It operates in the background without the developer being aware about it.
3
Data in .NET application is allocated on either stack(value types) or heap(reference types). Each time, new keyword is used to create an object, runtime performs the task of allocation and de-allocation of memory. It performs this as a background task, developer being unaware about it. Such type of garbage collection is called non-deterministic garbage collection. Developer can simply focus on business logic. The basic assumption on which garbage collector works is – “Memory is infinite�. Though this seems to be a false statement, the mechanism that is adopted by garbage collector does not allow the memory to get exhausted. Garbage collector uses Mark and Compact mechanism to manage the memory and make it available to the application for creation of new objects. Every application maintains the reference to the objects using a set of object pointers. These pointers point to the object currently in use or ones that are set to null. Initially garbage collector considers all objects as garbage. It traverses through the heap and builds a graph for the objects used in the application. The objects not in use are marked as garbage. Objects are allocated memory on heap as long as it is available. Heap maintains a pointer where the allocation for the next object takes place. The marked memory is freed by compacting the heap. The object pointers are re-validated. Even the pointer to the top of the heap where new memory allocations can take place is set. This is done by getting the information about the size of the object from the metadata of the application. Now the new memory allocation can take place on the top of the heap.
4
Garbage collection algorithm is fine-tuned to provide the best performance. This is done by using the concept of generations. This concept considers heap to be divided into 3 generations – 0,1 and 2. Newly allocated objects are considered to be short lived. But the objects allocated memory for longer duration are considered to be older objects. All objects are allocated memory in generation 0. These are the “young” objects. When the memory in generation 0 is full, garbage collection has to take place. Garbage collector checks for the used and unused(garbage) and builds the graph. The memory for the unused objects is reclaimed and the heap is compacted. The objects that survive this are considered to be in generation 1. If more objects are allocated on heap then their allocation once again is considered in generation 0 till it is full. Again the garbage collection takes place. Objects surviving this garbage collection are compacted and considered in generation 2. Objects in generation 1 and 2 are “old” objects.
5
GC manages only managed objects. Unmanaged resources like database connections, handles to files, etc. need to managed by the developer. This is done by writing a destructor for the class. The destructor helps to de-initialize the memory by freeing the unmanaged resources.
6
The destructor though appears to be the same as C++ destructors in a way that it is implicitly called. But the destructors in .NET are managed by garbage collector. When any class has a destructor in C#, it is implicitly translated to Finalize() method. The object of such class when created is put in an internal data structure called finalization queue. When such object is detected as garbage, it is transferred in yet another data structure called freachable queue. The object is not garbage at this stage because as the name “freachable� suggests that the finalized object is still reachable. A special thread runs to ensure the call to the Finalize() method for that object. Then the memory for that object is reclaimed. Thus the developer has no control over the destructor call. Destructor should be written only when it is required by the class. An empty destructor unnecessary affects the performance of the application. Also the objects with the Finalize() method remain in the memory for longer time. If such objects refer to other objects marked as garbage but without the Finalize() method, the lifetime of these objects is also prolonged.
7
Details of Finalization The objects are allocated memory on managed heap. If the class to which the object belongs, has a Finalize() method then pointers to such objects are added to finalization queue. In the above diagram I, C, E, F, I and J objects have Finalize() methods. So when they are created, pointers to these objects are added in the finalization queue. When garbage collection takes place, GC searches for the objects that are garbage (refer diagram II). Suppose B, E, G, J, H and I objects are garbage. GC checks whether any of the garbage have pointers in the finalization queue. E, I and J are such objects. They are immediately shifted to freachable queue. These objects are not treated as garbage objects at this stage. But the memory of other garbage objects such as B, G and H is reclaimed. The heap is compacted. A special thread runs calls the Finalize() method of E, I and J objects and then removes them from the freachable queue (refer diagram III). This special thread sleeps when there are no objects in the freachable queue. When GC runs again, it finds out that the objects in freachable queue are truly garbage. It then empties the queue by reclaiming the memory for the objects.
8
As mentioned earlier, garbage collection occurs automatically when the request for the memory cannot be satisfied with the available free memory. But sometimes as per the need of the application, garbage collection can be forced. This can be done by using Collect() method of GC class. GC class is responsible for controlling the garbage collection programmatically. Collect() method has 3 overloads as mentioned above. 1. Collect() method is used to reclaim all memory that is inaccessible. All garbage objects irrespective of their duration in memory from all generations are considered. Maximum amount of memory is reclaimed. 2. Collect(Int32) method is used to reclaim memory that is inaccessible in the generation passed as a parameter to the method. However there is no guarantee of this availability. 3. Collect(Int32, GCCollectionMode) method reclaims memory in the generation passed as a parameter to the method. The second parameter is an enumeration that specifies the behavior for a forced garbage collection. The values for the enumeration are: 1. Default – default setting that is currently Forced 2. Forced – forces garbage collection immediately 3. Optimized – Allows the garbage collector to decide whether the time is optimal Note: 1. The garbage collector does not collect objects with a generation number higher than specified by the generation parameter. Use the MaxGeneration property to determine the maximum valid value of generation. 2. Another method called SuppressFinalize() suppresses the call to the Finalize() method.
9
GC.SuppressFinalize() method SuppressFinalize() of GC class is used to suppress the Finalize() method of the object passed as a parameter to the method. Destructor or Finalize() method is used in implicit resource management. As mentioned earlier, Finalize() method is implicitly called by GC during garbage collection. If the same class also implements explicit resource management, then this method should be prevented from being called. Explicit resource management is done by implementing IDisposable interface. The Dispose() method can call SuppressFinalize() to suppress the call to Finalize(). (IDisposable interface covered in the next slide)
10
Explicit Resource Management Dispose Design pattern is provided by explicit resource management. The class that has code to handle unmanaged resources should implement IDisposable interface. This interface provides a Dispose() method. The code for cleanup is written in the Dispose() method. The object calls the Dispose() method. The developer has to take care of explicitly using this method as against the Finalize() method which is called implicitly. Resource resObject = new Resource(); ‌. ‌. resObject.Dispose(); If the base class has a Dispose() method implemented, then its Dispose() method is called from the derived class Dispose() method.
11
12
13
14
15
16