.NET Memory Management with dotMemory

Jason Schmidtlein .NET, .NET Core, Problem Solving, Technology Snapshot Leave a Comment

Given the maturity of the .NET Framework and the automated nature of its memory management, many developers are guilty of glossing over (or even outright ignoring) whether their code is optimal in terms of CPU and memory usage. Personally, I have caught myself making sure my code is maintainable, testable, and extendable while forgetting to consider memory management in terms of nonfunctional aspects.

While the .NET runtime does a great job and memory corruption is extremely rare, we should still be concerned with memory management, particularly in large-scale .NET base applications.

This concern isn’t limited to on-premise applications. It’s easy to forget about memory usage with cloud computing. Azure Functions and AWS Lambda have billing structures based upon the average memory size per second of function execution. The direct correlation between memory usage and cost couldn’t be more transparent.

Fortunately, there are many great tools to help profile and analyze your memory footprint. JetBrains has a fantastic tool called dotMemory which makes it easy to profile processes, auto detect issues, perform deep analysis, and determine traffic. dotMemory can be installed as either a stand-alone tool or as a part of the ReSharper package integrated into Visual Studio.

In this post, we’ll show how to use dotMemory to generate a memory profile and analyze a memory leak in a .NET Core application.

Our dotMemory Example

For example, I have the following code hidden away in a console application.

 internal class Program
 {
     private static readonly List<Car> Cars = new List<Car>();
     public static void Main(string[] args)
     {
        var stringGenerator = new RandomStringGenerator();
        var yearGenerator = new Random();
        while (true)
        {
           var year = yearGenerator.Next(1900, 2020);
           var make = stringGenerator.Generate();
           var model = stringGenerator.Generate();
           Cars.Add(new Car { Make = make ,Model = model ,Year = year});
           Console.WriteLine(value:$”{Cars.Count}: year:{year} make:{make} model:{model}”);
        }
     } 
 }

This is an infinite loop that adds Car class to a static list causing a memory leak. If I had a large application and needed to find out what was happening, I could start my application in Visual Studio with a profiler attached to it on startup.

See Also:  Spring Boot Profiles: A Strategic Way to Configure Applications

After running my application, the following memory profile was generated.

As you can see, the total amount of memory used continued to climb until I killed the process. While the process was running I took two snapshots so that I could dive into the details of the profile.

Looking into Snapshot #2, we first get a general overview from the automatic memory inspection from dotMemory. As you can see the type string and Car make up a large portion of the heap size.

I can also change views on the analysis to get a better idea of how the heap is organized. When I group by type I again see that String and Car make up a large number of instances. If I stopped at this point I might think that I have two memory leaks: one case where many instances of String are leaking and another where Car is leaking.

Fortunately, dotMemory provides other views of analysis so that I can pinpoint the issue. If I change the snapshot profile view to “Dominators,” I’m treated to a nice chart and tree which gives a better picture.

It turns out that nearly the entire object set memory profile is dominated by a list of Car models. The strings we were seeing in the other views were just children to the car classes and actually not a separate leak.

Now that I know a list of Car models is the source of a memory leak, I can determine where the leak is occurring in my code.

If I click on instances, I see the car list at the top.

When clicking on the car list/array, I’m talking to the analysis of the instance. The analysis on the instance again provides several metrics on an instance in memory, but I am interested in knowing where the instance can be found.

See Also:  Angular and Swagger: Experiences Learned

If I click on the “Create Stack Trace” tab I can see that it can be found in the Program.Main function in the MemoryLeak namespace.

A memory leak like the example just provided would be obvious to anyone executing the application because it would fail quickly and consistently. It is much harder to detect a leak that takes days or weeks to cause a crash…or a leak that doesn’t necessarily cause a crash at all but is just taking up a lot of memory.

Fortunately, dotMemory can be used to profile applications running outside or in Visual Studio. The following are some of the profiling options.

Other Options

While I presented the dotMemory profiler by JetBrains, please be aware that there are many other great alternatives.

Red-gate has a profiler with similar features and price point:

https://www.red-gate.com/products/dotnet-development/ants-memory-profiler/index

Microsoft provides a free CLR profiler for .NET 4:

https://www.microsoft.com/en-us/download/details.aspx?id=16273

Visual Studio 17 has a built-in memory profiler and there are a number of free profilers available on the Microsoft marketplace.

Wrap Up

It is easy to forget about memory consumption in large-scale applications. Using a profiler makes what was once a daunting task much easier and ultimately helps with the scalability and costs of enterprise applications.

Hopefully, I have convinced you to consider profiling memory usage in your applications!

What Do You Think?