While the .Net Common Language Runtime (CLR) abstracts away memory management from developers, memory leaks are still possible. This programming tutorial talks about memory leaks, why they occur, and the best practices that can be followed to avoid them in ASP.NET applications.
Looking to learn .NET in a classroom or online course environment? Check out our list of the Top Online Courses to Learn .NET.
What are Memory Leaks and Why Do They Occur?
A memory leak happens when an application cannot correctly manage memory allocations and deallocations. Once a memory leak has occurred, the Garbage Collector (GC) cannot remove objects that are no longer in use from memory. Typically, a memory leak happens when an application fails to release memory for objects no longer needed, leading to memory fragmentation and degraded performance.
If the application frequently allocates memory to new objects, but fails to deallocate it when it is no longer needed, memory use grows over time, which may cause an application to crash at some point.
Memory leaks can occur in managed heaps, unmanaged heaps, and even stacks. As soon as the execution of a method is completed, the memory allocated in the stack is usually recovered.
Often a stack frame may not be released particularly if your app extensively uses the stack. When this happens, the stack might be leaked by your application’s threads.
A memory leak can also happen when unmanaged code fails to deallocate unavailable memory, which may not have been allocated anymore by any process at the point of the allocation.
What Happens When a Memory Leak Occurs?
When memory leaks occur, references to objects persist even if those objects are no longer used. Rather than recovering the memory, the GC promotes the objects to higher generations since they are still alive and referenced.
This type of promotion is not only expensive, but it also keeps the GC overburdened. Whenever memory leaks occur, more and more memory is used until there is no more available memory. As a result, the Garbage Collector must perform more frequent collections to free up memory.
How to Avoid Memory Leaks in ASP.NET and .NET
Below are a few best practices programmers can use to avoid memory leaks in their ASP.NET, C#, and .NET applications.
Release Objects as Soon as You’re Done Using Them
Developers must avoid keeping references to managed and unmanaged objects longer than necessary. While this may not seem like a memory leak, memory usage grows when an application retains references for longer than required, and an “Out of Memory”</b exception may be thrown. Once you are done using an object (managed or unmanaged), you must set it to null.
If you are using a managed object, this will make your object ready for garbage collection. If you are using an unmanaged object, by setting the object to null, the resources occupied by the object would be released soon after. The golden guideline to remember is to acquire resources late and release them early.
Avoid Large Object Heap Fragmentation
In .NET, the Large Object Heap (LOH) is a segment of memory that is used to store large objects – typically objects that are larger than 85K in size. The LOH is part of the managed heap and can be accessed using the System.Runtime.InteropServices.Marshal class. Note that, contrary to the Small Object Heap (where objects less than 85K are stored), the Large Object Heap is never compacted by the CLR.
Large object heap fragmentation is a condition where the LOH has become broken up into many small blocks, rather than being stored in one continuous block of memory. This can make it difficult to store new large objects or reuse existing ones, because they have to be stored in small fragments instead of one large block.
The fragmentation of large object heap occurs when the objects are not stored contiguously in memory and the space allocated for the large objects is broken into small chunks, which are not enough to store large objects. This leads to wastage of space and also increases the time taken for garbage collection because more pages need to be moved during garbage collection if they are not stored contiguously on disk or RAM.
The main reason for fragmentation of large object heaps is due to frequent allocation and deallocation of memory. This results in an increase in time spent by the garbage collector during minor collections and major collections because it needs to relocate objects within the LOH to make room for other objects being allocated into it, which could greatly affect performance.
Reducing fragmentation of large object heaps is important to ensure that you have an optimal application performance. Developers can avoid large object heap fragmentation by pinning and compaction. However, the best practice is to avoid creating objects in the large object heap by creating smaller objects (i.e., objects that have a size less than 85K). To prevent fragmentation of the Large Object Heap, you can use the “Large Object Heap Compaction” feature.
To prevent fragmentation, programmers can use two methods: Use pinning, or, pre-allocating large objects on their own section of memory, which prevents them from being moved around within their allocated space so they can be accessed quickly without having to recreate them each time they are needed again later on down the road; use compacting techniques where allocating fewer small objects will prevent them from fragmenting at all times.
You can force large object heap compaction in ASP.NET using the following code example:
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect();
However, this is not recommended and should be avoided because of performance impacts.
Looking for a tool to help monitor application performance? We have a list of the Top Application Performance Monitoring (APM) Tools to help you get started.
Avoid Using Static References
In an application, objects referenced by static references reside in memory until the application is active. These objects reside in memory for the application’s lifetime or until the reference to the static object is marked as null.
It should be noted that GC does not collect GC Root objects and the GC considers static variables as GC Root objects. This explains why static variables are not garbage collected during a garbage collection cycle.
The takeaway here is to avoid using static references unless essential. When you use static references, be cautious about marking them as null when they are no longer required.
Delete Unmanaged Objects When You Are Done Using Them
While objects residing in the managed heap are deleted by the garbage collector, unmanaged objects are not cleaned by the garbage collector. Hence, developers must delete unmanaged objects (file handles, database connections, COM objects, etc) explicitly once they are done using them. In other words, if your application makes use of unmanaged objects, you must take special care to ensure that they are disposed in your code, ideally using the Dispose method. It is important to note that finalizers are not guaranteed to be invoked by the CLR.
Final Thoughts on ASP.NET Memory Leaks
In addition to performance concerns, memory leaks also create GC pressure, negatively impacting application performance. Many developers use the Windows Task Manager to confirm whether or not an app has memory leaks. There are cases where your app crashes without returning the OutOfMemoryError message, which makes diagnosing a memory leak a problem more difficult to identify and detect.