Understanding and Troubleshooting Unmanaged Memory Usage in .NET



Writing in C# every day, we forget that we are in a privileged world. Underneath the abstraction of the virtual machine lies a batch of C++ code that is handling memory in the old fashioned way. Blocks of memory are allocated by asking a heap manager for a chunk of memory - you get a pointer to it and you can do exactly what you want with that memory. There's no associated type controlling your access to the memory and you're free to do what you like with it. Unfortunately that also means that you can write outside its bounds, or over any header that the heap manager has associated with the block. You can free the block and continue to use it too. All of these problems can lead to spectacular crashes.

Over time, patterns have been developed to handle some of these issues. C++ programs for example often encapsulate memory allocation using the RAII pattern, where blocks of memory are allocated for a particular lexical scope within the program. When the scope is exited, the destructor on a stack allocated object can ensure that the memory is released, and the object's API can ensure that the programmer does not get unrestrained access to the raw memory itself.

How unmanaged memory works, and when you use it
There are two ways to allocate unmanaged memory from .NET - by using the VirtualAlloc and VirtualFree functions, or by using AllocateHeap.
VirtualAlloc/VirtualFree lets you reserve a region of (4K) pages. You can choose between reserving chunks of the virtual address space, and allocating actual physical memory. The AllocateHeap API provides a heap manager that deals with small blocks of memory. This takes care of grabbing chunks from the operating system and handles memory management to avoid problems like fragmentation.
The C Runtime Library then has malloc and free functions that operate at a higher level, allowing it to do additional bookkeeping and debugging while keeping things portable.
Together, these tools let you manipulate unmanaged memory from within your managed application. There are essentially four different uses for that:
Heaps for the garbage collector, on which the managed objects live.
Data structures that the managed runtime uses to run the managed code. This includes space taken up by JITted code as well as other metadata.
Resources associated with managed objects, particularly those required for interoperability with the operating system, such as bitmaps.
Memory used by unmanaged components which are used by your managed code, e.g. third-party COM components.
Processes operate entirely within their virtual memory space, and do not usually control where the regions of memory they are using reside. The operating system manages which regions of virtual memory are held in physical memory (comprising the process working set) and which exist only on the hard drive. Additionally, pages in virtual memory may either be private, meaning they are accessible only to a particular process, or be shareable between multiple processes. Assemblies, DLLs, and mapped files can be shared, but the .NET heaps, JITted code, and most other run-time data is private.
How much is being used by what?
By default, Windows Task Manager shows the size of the private working set of a process, which consists of those memory pages which are both private and reside in physical memory. Pages may be moved in and out of the working set dynamically by the operating system, depending on how they are accessed and the amount of physical memory available.
ANTS Memory Profiler shows a breakdown of the total number of private bytes in virtual memory in a pie chart on the summary screen, regardless of whether they are in physical memory or not. The "unmanaged" section of the pie chart therefore includes JITted code, CLR metadata, and other unmanaged resources and memory allocations which are not shareable.
The CLR itself must allocate unmanaged memory to run your application. Some of this is for the garbage collector heaps that the objects are created on, which are displayed on the pie chart in ANTS Memory Profiler. The objects that you see in the class and instance lists all reside within these heaps. The CLR also requires unmanaged memory to store JITted code and perform internal bookkeeping. As a result, in applications which do not make significant use of unmanaged components, the CLR is usually responsible for...
comments powered by Disqus