How to avoid memory leaks in .Net applications

Learn the causes of memory leaks in .Net, how to detect them, and how avoid them in your applications

How to avoid memory leaks in .Net applications
Maarten Van Damme (CC BY 2.0)

If you have developed C or C++ applications, then you are no doubt aware of memory leaks and their pitfalls. Although the .Net CLR abstracts memory management from the developer, memory leaks can happen in your .Net applications as well. In this article, we’ll look at the causes of memory leaks in .Net applications, how we can detect them, and the strategies we can use to avoid them.

The first thing to note is that you can have memory leaks in the managed heap, the unmanaged heap, and even on the stack. Memory allocated in the stack is generally reclaimed once the execution of the method is completed. However, there may be situations in which your application makes heavy use of the stack and the stack frame is never released. Such conditions may lead to leaking the thread stack.

The .Net CLR (common language runtime) allocates objects in the managed heap and releases them when they are no longer needed by the application. However, keep in mind that the runtime only releases objects in the managed heap that are unreachable. In other words, if your application has a reference to an object in the managed heap, that object will not be cleaned up by the GC (garbage collector). 

Release objects promptly

So, the first rule is to avoid holding references to managed objects longer than necessary. While this might not seem to be a memory leak, when an application holds references longer than necessary, memory consumption increases and an “Out of Memory” exception may result.

It is good practice to create and use objects that are local in scope. Once you are done using the object, you can set it to null to inform the GC that the object is no longer needed. The golden rule that you should follow is this: Acquire resources late and release them early.

Keep an eye on worker threads

Improper thread management can also be a cause of memory leaks. Because the garbage collector does not clean up the stack, it is the developer’s responsibility to do so by explicitly releasing the worker threads that are no longer needed. As soon as your application is done using the worker threads, you should terminate them.

Note that, when working with multithreading, you should not rely on finalizers to clean up the resources because finalizers are not deterministic. Rather, you should clean up the resources explicitly by making a call to your custom Dispose method. You should also avoid using Thread.Abort unless you have a specific reason to use it.

Another strategy you can adopt to avoid memory leaks in the thread stack is to limit the number of simultaneous threads or using a thread pool to handle thread management.

Avoid static references

Note that objects that are referenced by static objects are never released from memory. This also holds true for any object that is being indirectly referred to by a static field. Such objects will remain in memory for the life of the application or until the static object is marked as null, whichever comes first. This is because the object referring to the memory location is itself static.

The lesson here is to avoid static references unless they are absolutely necessary. When you do use static references, be diligent about marking them null when they are no longer needed.  

Clean up unmanaged objects

While managed objects (at least those in the small object heap) are cleaned up by the garbage collector, unmanaged objects have to be freed explicitly. If your application uses unmanaged objects—such as file handles, database connection objects, and COM objects—you must take extra care to ensure that they are cleaned up in your code, preferably by using the Dispose method. Note that finalizers are not guaranteed to be called by the runtime.

Avoid large object heap fragmentation

Managed memory can also leak when you have fragmentation of the heap. Basically, the .Net runtime manages two different types of heaps—the small object heap (SOH) and the large object heap (LOH). While small objects (typically less than 85 kilobytes in size) are allocated in the SOH, larger objects are allocated in the LOH. Unlike the SOH, the large object heap is never compacted, so there is always a possibility of memory loss due to memory holes. Although there are ways to clean up the LOH, it is up to the developer to be aware of the costs of memory allocation for large objects. See my article on how to (not) use the large object heap

How to track down memory leaks

There are plenty of tools available to help you track object instances and handles and detect memory leaks. These tools are among the most popular:

  • ANTS Profiler
  • dotTrace 
  • GDIView
  • .Net Memory Profiler
  • WinDbg

The easiest way to determine where memory is leaking (from the heap, unmanaged heap, or stack) is to use the Perfmon tool (which comes free with Windows) to examine the following performance counters:

  • Process / Private bytes
  • .Net CLR Memory / # bytes in all heaps
  • .Net CLR LocksAndThreads / # of the current logical thread

If you notice that Process / Private bytes is increasing but .Net CLR Memory is not increasing, you can assume that the memory leak is happening in unmanaged memory. On the contrary, if you observe that both of those counters are increasing, then it is apparent that the memory leak is in managed memory.

A good understanding of the internals of garbage collection and the intricacies of memory management can help you employ strategies to avoid memory leaks in .Net applications. Although the intricacies of how the managed memory is garbage collected are abstracted from us, there are still steps we can take to avoid unnecessary memory consumption and unnecessarily large memory footprints.

Copyright © 2017 IDG Communications, Inc.