I have seen one video from red-gate about the memory management in .net when I was surfing about ANTS memory profiler. So I thought why not I create article based on this facts & help others to know how Garbage collector actually works.
The Microsoft .NET common language runtime requires that all resources be allocated from the managed heap. Objects are automatically freed when they are no longer needed by the application.
When a process is initialized, the runtime reserves a contiguous region of address space that initially has no storage allocated for it. This address space region is the managed heap. The heap also maintains a pointer. This pointer indicates where the next object is to be allocated within the heap. Initially, the pointer is set to the base address of the reserved address space region.
Garbage collection in the Microsoft .NET common language runtime environment completely absolves the developer from tracking memory usage and knowing when to free memory. However, you’ll want to understand how it works. So let’s do it.
We will cover the following topics in the article
- Why memory matters
- .Net Memory and garbage collection
- Generational garbage collection
- Temporary Objects
- Large object heap & Fragmentation
- Memory problems
1) Why Memory Matters
Insufficient use of memory can impact
- Other applications
Hidden problem in code can cause
- Memory leaks
- Excessive memory usage
- Unnecessary performance overhead
2) .Net Memory and garbage collection
.Net manages memory automatically
- Creates objects into memory blocks(heaps)
- Destroy objects no longer in use
Allocates objects onto one of two heaps
- Small object heap(SOH) – objects < 85k
- Large object heap(LOH) – objects >= 85k
You allocate onto the heap whenever you use the “new” keyword in code
Small object heap (SOH)
- Allocation of objects < 85k – Contiguous heap – Objects allocated consecutively
- Next object pointer is maintained – Objects references held on Stack, Globals, Statics and CPU register
- Objects not in use are garbage collected
Next, How GC works in SOH?
GC Collect the objects based on the following rules:
- Reclaims memory from “rootless” objects
- Runs whenever memory usage reaches certain thresholds
- Identifies all objects still “in use”
- Has root reference
- Has an ancestor with a root reference
- Compacts the heap
- Copies “rooted” objects over rootless ones
- Resets the next object pointer
- Freezes all execution threads during GC
- Every GC runs it hit the performance of your app
3) Generational garbage collection
Optimizing Garbage collection
- Newest object usually dies quickly
- Oldest object tend to stay alive
- GC groups objects into Generations
- Short lived – Gen 0
- Medium – Gen 1
- Long Lived – Gen 2
- When an object survives a GC it is promoted to the next generation
- GC compacts Gen 0 objects most often
- The more the GC runs the bigger the impact on performance
Here object C is no longer referenced by any one so when GC runs it get destroyed & Object D will be moved to the Gen 1 (see figure-5). Now Gen 0 has no object, so when next time when GC runs it will collect object from Gen 1.
Here when GC runs it will move the object D & B to Gen 2 because it has been referenced by Global objects & Static objects.
Here when GC runs for Gen 2 it will find out that object A is no longer referenced by anyone so it will destroy it & frees his memory. Now Gen 2 has only object D & B.
Garbage collector runs when
- Gen 0 objects reach ~256k
- Gen 1 objects reach ~2Meg
- Gen 2 objects reach ~10Meg
- System memory is low
Most objects should die in Gen 0.
Impact on performance is very high when Gen 2 run because
- Entire small object heap is compacted
- Large object heap is collected
4) Temporary objects
- Once allocated objects can’t resize on a contiguous heap
- Objects such as strings are Immutable
- Can’t be changed, new versions created instead
- Heap fills with temporary objects
Let us take example to understand this scenario.
Figure – 8
After the GC runs all the temporary objects are destroyed.
5) Large object heap & Fragmentation
Large object heap (LOH)
- Allocation of object >=85k
- Non contiguous heap
- Objects allocated using free space table
- Garbage collected when LOH Threshold Is reached
- Uses free space table to find where to allocate
- Memory can become fragmented
After object B is destroyed free space table will be filled with a memory address which has been available now.
Now when you create new object, GC will check out which memory area is free or available for our new object in LOH. It will check out the Free space table & allocate object where it fit.
6) Object Finalization
- Disk, Network, UI resources need safe cleanup after use by .NET classes
- Object finalization guarantees cleanup code will be called before collection
- Finalizable object survive for at least 1 extra GC & often make it to Gen 2
- Finalizable classes have a
- Finalize method(c# or vb.net)
- C++ style destructor (c#)
Here are the guidelines that help you to decide when to use Finalize method:
- Only implement Finalize on objects that require finalization. There are performance costs associated with Finalize methods.
- If you require a Finalize method, you should consider implementing IDisposable to allow users of your class to avoid the cost of invoking the Finalize method.
- Do not make the Finalize method more visible. It should be protected, not public.
- An object’s Finalize method should free any external resources that the object owns. Moreover, a Finalize method should release only resources that are held onto by the object. The Finalize method should not reference any other objects.
- Do not directly call a Finalize method on an object other than the object’s base class. This is not a valid operation in the C# programming language.
- Call the base.Finalize method from an object’s Finalize method.
Note: The base class’s Finalize method is called automatically with the C# and the Managed Extensions for C++ destructor syntax.
Let see one example to understand how the finalization works.
Each figure itself explain what is going on & you can clearly see how the finalization works when GC run.
For more information on Finalization refer the following links:
How to minimize overheads
Object size, number of objects, and object lifetime are all factors that impact your application’s allocation profile. While allocations are quick, the efficiency of garbage collection depends (among other things) on the generation being collected. Collecting small objects from Gen 0 is the most efficient form of garbage collection because Gen 0 is the smallest and typically fits in the CPU cache. In contrast, frequent collection of objects from Gen 2 is expensive. To identify when allocations occur, and which generations they occur in, observe your application’s allocation patterns by using an allocation profiler such as the CLR Profiler.
You can minimize overheads by:
- Avoid Calling GC.Collect
- Consider Using Weak References with Cached Data
- Prevent the Promotion of Short-Lived Objects
- Set Unneeded Member Variables to Null Before Making Long-Running Calls
- Minimize Hidden Allocations
- Avoid or Minimize Complex Object Graphs
- Avoid Preallocating and Chunking Memory
7) Common Memory Problems
- Excessive RAM footprint
- App allocates objects too early or for too long using more memory than needed
- Can affect other app on system
- Excessive temporary object allocation
- Garbage collection runs more frequently
- Executing threads freeze during garbage collection
- Memory leaks
- Overlooked root references keep objects alive (collections, arrays, session state, delegates/events etc)
- Incorrect or absent finalization can cause resource leaks
References & other useful articles related to this topic:
Hope this help