Memory management in C#

With respect to memory management, memory is divided into 3 types.

Long memory 
  • Static members are stored.
  • No talk of memory management.
Short Memory
  • Don't need memory management.
  • Data are stored in stack data structure.
Dynamic memory
  • Data are stored in heap data structure.
  • Large object heap i.e. > 85 KB doesn't need memory compaction.
  • Small object heap i.e. < 85 KB need memory compaction
If you have studied memory compaction in Operating system in engineering, you can understand the above point easily. For who don't know memory compaction here is a brief description.

 Memory compaction
  • Memory is allocated in different ways.
Refer  http://en.wikipedia.org/wiki/Memory_management_%28operating_systems%29



B1 -10 MB
B2 -10 MB
B3 -10 MB
B4 -20 MB
B5 -30 MB
B6 40 MB

  • Consider this memory block. Now block B7 which is of 40 MB  wants memory. Memory Management Unit will check for unused memory. B3 and B5 are not used. It will be removed. Now we have 2 memory blocks empty, but B7 doesn't fit into any block.

B1 -10 MB
B2 -10 MB
Empty -10 MB
B4 -20 MB
Empty -30 MB
B6 40 MB
  •  So memory has to be compacted. The higher memory will be moved upper (lesser memory address) and rearrangement of memory blocks is done. In such case every block will have 2 pointers start and end. Those will be changed. Now we have 40 MB block which is enough for B7.


B1 -10 MB
B2 -10 MB
B4 -20 MB
B6 40 MB
Empty -40 MB

Memory Management

In CLR via C# which is the back bone of my studies related to dot net framework has explained the process of memory  management in the following way.

  1.  Allocate memory for the type that represents the resource (usually accomplished by using
    C#’s 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.
Generally 4th step is not required unless it is a special case like files, sockets or database connections.

Garbage collector working :

To study these steps we need to understand how Garbage collector works.

  • Garbage collection is a mark and prepare/sweep engine.
  •  It will collect reachable object graph from roots and will check for reachability. It will only check for reference type variables and  will also check in reference locations like thread stack for static references and reachable object in registry object.
  • Those which are reachable from root are marked and which are not reachable are not marked.
  • Now which are marked are compacted and pushed to down in the heap.
  • When compacting, CLR will take care the new address will be referred in the referring location otherwise memory might be corrupted.
  •  CLR subtracts from each root the number of memory it shifted and will store the new address.
  • Most of the out of memory exception happens when Garbage collector is unable to allocate new memory in process memory block because all the memory is reachable.
Generation algorithm :


GC works on generation to improve performance. There are 3 generations available in GC.

  • Data are divided as small object heap and large object heap data.
  •  Heap works as stack w.r.t Garbage collection. As memory is not there in Gen0 then objects are moved to Gen1 then Gen1 to Gen 2.
  • All new objects are stored in Generation 0.
  • Consider an example where the space in Generation 0 is full and all are reachable. Now a new object request for space, then some of the objects with larger life time will be moved to Generation 1.
  • Suppose in above case if some are not reachable, they will be erased and blocks will be compacted and new block will be given memory.  (Same as the example explained in memory compaction.)
  • Now Generation 0 items are moved to Generation 1 and generation 1 is full. Now garbage collection occurs and survived objects will be moved Generation2.
  • Garbage collection occurs only when the generation reaches its maximum limit.
  • Only the objects which survive garbage collection will be moved to next generation.
  • Generation 2 will be occupied by LOH i.e. large object heap.  There is no generation 3 and generation 2 items take more than 2 garbage collections to be removed from memory.
Note : 
  1.  Every empty object occupies 12 bytes. 4 bytes for object header, 4 bytes for  method table or type handler and 4 bytes will be for object which will be empty.
  2. Every time a new object is called it might call gc.
  3. When generation reaches threshold,  it might call gc.
  4. Generation algorithm is a cumulative process. Garbage collection  on generation 2 applies to Generation 2,1,0. Garbage collection  on generation 1 applies to Generation 1,0.Garbage collection  on generation 0 applies to Generation 0.
  5. GC.Collect() may or may not call gc.
  6. GC.Handle.Pinning() might increase fragmentation.
  7. You can refer Chapter 21, The Managed heap and Garbage collection of CLR via C#. 



Debugging memory :



You can debug whether the data is garbage collected or not using commands.

Following are the commands you can type in Immediate Window while debugging to check the status of memory. SOS Debugging Extension is required to use below commands. You can download Sosex.dll

1.       eeheap –gc    :- Displays information about processed memory consumption
2.       Dumpheap – stat   Displays information about the garbage-collected heap and collection statistics about objects.
3.       !clrstack  -a                                     Provides the stack trace of managed code.
4.       !dumparray memory-address     Examines the array elements.
5.       GC.GetGeneration(object)           Gives the generation of the given object.                              
6.       !objsize memory-address             Gets the total size of the object in the address.
7.       !gcroot memory-address             Displays information about roots or any references for the object specificed.
8.       !finalizequeue – Command to see finalize queue.
 
Dispose and Finalize :
   


I have mentioned about some special cases such as database operations, file operations, windows handle e.t.c. In such cases we need to take care of freeing memory resources. There are 2 destructors to free resources. They are dispose and finalize.


Dispose
Finalize
Deterministic
Non deterministic
Explicit call
Implicit call
Asynchronous/Async
A-sync (one thread in clr is reserved for calling finalize for all objects – Finalizer)
One GC cycle is enough to remove the object  from memory.
Min 2 GC and more than that.
 

How Dispose works ?
  • Calling a dispose doesn’t mean the memory is cleared.
  • IDispose determines object is  disposable or not. Otherwise no use of IDisposable.
  • Dispose pattern is used to restrict application from calling multiple dispose. 

Dispose example

How Finalize works ? (~ClassName)



  • Finalizer maintains 2 data structure. One is finalize queue and another one is FReachable queue.
  • Finalize queue contains the address for the finalize method and the entry is done during new object is created.
  • If object is not reachable, it will check for finalize table. If it is present, it will move it to FReachable queue. Since queue takes items one at a time , finalize can take 2 or more than 2 GC cycles.
  • Command !finalizequeueis used to see the finalize queue.

Reference :
  1. CLR via C# 4th edition by Jeffrey Richter
  2. Operating Concepts by Silberschatz, Galvin, Gagne.
  3.  https://msdn.microsoft.com/en-us/library/bb190764(v=vs.110).aspx
  4. http://blogs.msdn.com/b/johan/archive/2007/01/11/i-am-getting-outofmemoryexceptions-how-can-i-troubleshoot-this.aspx
  5. https://msdn.microsoft.com/en-us/library/vstudio/b1yfkh5e%28v=vs.100%29.aspx




No comments:

Post a Comment

Pages