Exploring Different Stages of Memory Management in .NETMemory Lifecycle of a .NET application will fit into three stages of memory management which are dealt in detail in this article. Stage 1: Initial Memory Allocation When you declare any object or any primitive type, memory has to be initially allocated to it. Memory allocation can be categorized into three different types: Static
Memory Allocation
.NET uses last two modes of memory allocation. Your .NET application will have memory allocated in both stack and heap. All primitive types excluding String are allocated in stack. To generalize it further, if your object is of type ValueType or any of its derivatives and the object is not boxed, then it is stored in stack. All other data structures that cannot fit into stack memory are allocated in heap memory where in object allocation and deallocation is done in a random way. Stage 2: Memory Utilization Memory utilization means that the memory that is no longer in use has to be reused. Memory utilization can happen in two different ways: Simple: When the memory your program requires is readily available as one continuous block, then the memory utilization is simple Complex: When the memory your program requires is not available in continuous block but it is available as small memory blocks in different memory areas, then compaction has to happen and after that memory has to be allocated. In this case, memory utilization becomes complex Stage 3: Compaction and Reuse In stack based memory allocation, memory is allocated in First In First Out basis. The allocations happen in an orderly way, ie. memory is allocated to the primitive types at the method start and are deallocated at the end of the method. The corresponding memory is removed from the top of the stack and not in between. So there is no chance of fragmentation and hence compaction is not required. In heap based memory allocation, both stage 2 and stage 3 has complexities. Since objects are released in random order, different blocks of memory will be available in bits and pieces. For example if your object requires 24 bytes, heap memory has those bytes but not in one continuous area. Then what will happen? Since there is no continuous block of storage available, the object cannot be allocated in memory and OutOfMemoryException will occur. This is known as Fragmentation. These freed non-continuous blocks have to be assembled together as one large block so that memory utilization is simple. This is achieved using Compaction. Garbage collector has reference counter to determine the objects that are no longer in use and then the memory allocated to the object is freed. Garbage collector has yet another component called compaction engine which takes care of compaction. Consider the example mentioned earlier, if a new object requires 24 bytes that are available in the memory but not continuously, as shown below: In the above diagram, each block represents 8 bytes of memory. Here, the first two blocks are free and last block is free. If they are all available together then the object requiring 24 bytes can be allocated. Compaction engine solves the problem and the memory area will now look like: Now the new object can be easily allocated. However occurrence of fragmentation and then performing compaction is a performance concern and its an expensive task. You can avoid it with the help of an optimization technique called Object Pooling which aims at reusing objects rather than allocating and deallocating them frequently.
|