Memory Leaks In .NET Application Using Dump File

In this blog, I will be demonstrating how to find memory leaks in a .NET application using a dump file.

I will be demonstrating this in multiple steps which include identification of memory leaks and then moving into the details of how to find those references which are not released by using the dump file.
 
In step 1, I will create a .NET console application to have logic which has memory leaks and then will identify via 'performance monitor' a default Windows tool to detect and finally, I'll use a dump file to find out all the unrelased references.
 
Here is a code snippet of the application "memoryleakdemo' which has memory leaks. It opens 10K instances of file stream but none is disposed of.
  1. class Program  
  2.     {  
  3.         static void Main(string[] args)  
  4.         {  
  5.             IList<Company<Employee>> companies = new List<Company<Employee>>();  
  6.             var location = new Uri(Assembly.GetEntryAssembly().CodeBase).LocalPath;  
  7.             var path = Path.GetDirectoryName(location);  
  8.             var filePath = args.Any() ? args[0] : Path.Combine(path, "Resource\\Companydata.txt");  
  9.             var mainStream = new StreamWriter(filePath);  
  10.   
  11.             var i = 0;  
  12.             // 1. memory leak : Create steam for each company but not disposed.   
  13.             // 2. event subscribed not released.                 
  14.             while (i++ <= 10000)  
  15.             {  
  16.                 foreach (var item in new List<string> { "DX Generation""Nxt""Dudley Boyz" })  
  17.                 {  
  18.                     var company = new Company<Employee>(item);  
  19.                     company.OnNewJoinne += e =>  
  20.                     {  
  21.                         CompanyOnNewJoinne(e, mainStream);  
  22.                     };  
  23.                     foreach (var item1 in new List<string> { "employe1""employe2" })  
  24.                     {  
  25.                         company.AddNewEmployee(item1);  
  26.                     }  
  27.   
  28.                     companies.Add(company);  
  29.                 }  
  30.             }  
  31.   
  32.             mainStream.Dispose();  
  33.             Console.ReadKey();  
  34.         }  
  35.   
  36.         private static void CompanyOnNewJoinne(EmployeeJoinedEventArgs e, StreamWriter stream)  
  37.         {  
  38.             Console.WriteLine($"{e.EmployeeName} employee Joined {e.EmployerName}");  
  39.             stream.WriteLine($"{e.EmployeeName} employee Joined {e.EmployerName}");  
  40.         }  
  41.     }  
  42.   
  43.     public class Company<T> where T : Employee  
  44.     {  
  45.         public delegate void EmployeJoined(EmployeeJoinedEventArgs name);    
  46.         public event EmployeJoined OnNewJoinne;    
  47.         public string Name { getprivate set; }    
  48.         public StreamWriter FileStream { getprivate set; }    
  49.         private readonly IList<T> employee = new List<T>();  
  50.   
  51.         public Company(string name)  
  52.         {  
  53.             this.Name = name;  
  54.             var location = new Uri(Assembly.GetEntryAssembly().CodeBase).LocalPath;  
  55.             var path = Path.GetDirectoryName(location);  
  56.             // each object have fileStream but no dispose.  
  57.             // you can fix it via Idispose implementation to dispose FileStream  
  58.             this.FileStream = File.CreateText(Path.Combine(path, Path.GetRandomFileName() + ".txt"));  
  59.         }  
  60.   
  61.         public bool AddNewEmployee(string name)  
  62.         {  
  63.             if(employee.FirstOrDefault(s => s.Name == name) != null)  
  64.             {  
  65.                 return false;  
  66.             }  
  67.   
  68.             employee.Add((T)Activator.CreateInstance(typeof(T), name));  
  69.   
  70.             // Write in local stream.  
  71.             FileStream.WriteLine($"{name} employee Joined {this.Name}");  
  72.   
  73.             // write in main stream.  
  74.             OnNewJoinne?.Invoke(new EmployeeJoinedEventArgs(this.Name, name));  
  75.             return true;  
  76.         }  
  77.     }  
  78.   
  79.     public class Employee  
  80.     {  
  81.         public string Name { getprivate set; }  
  82.         public Employee(string name)  
  83.         {  
  84.             this.Name = name;  
  85.         }  
  86.     }  
  87.   
  88.     public class EmployeeJoinedEventArgs : EventArgs  
  89.     {  
  90.         public EmployeeJoinedEventArgs(string companyName, string employeeName)  
  91.         {  
  92.             EmployerName = companyName;  
  93.             EmployeeName = employeeName;  
  94.         }  
  95.   
  96.         public string EmployerName { getprivate set; }  
  97.         public string EmployeeName { getprivate set; }  
  98.     }  
You could notice that each company object has filestream object but it's not disposed of, that is, one produces a memory leak. 
 
Let's move to the second step to confirm whether this application really has any memory leaks or not using the default 'performance monitor' tool that comes free with Windows. There are many tools available in the market to analyze memory leaks, you can also use the VS performance profiler which also gives to memory allocation, CPU sampling, etc.  
 
Type 'performance monitor' on Windows search and open this tool. Select performance monitor and add a new counter for this application to analyze gen0, gen1, & gen2 object and once the execution is over, check if the memory/object in different generations is reduced or not. 
 
If you notice Gen1 or Gen2 size increasing consistently or Gen0 and gen1 are not reduced even after the execution is over, as shown in the picture, this is self-explanatory that the application has memory leaks. GC automatically does the job for collecting managed object automatically and disposing of it if it is not referenced by any root.
 
 
Let's move to the final and main step of this article, i.e., finding out references that are not released; and with their roots using dump file and opening that dump file in Visual Studio to debug it.
 
Go to your task manager and find this application via the name and right-click on it to select 'Create dump file'. Your dump file will be created in a temp folder. Copy that path and open in Visual Studio.
 
 
Open VS and select 'Open file' and use the same dump file path to open the file. As this application is only managing managed memory, hence we can select the option 'debug Managed memory'. If you are using any third party DLLs etc., then you can use the symbol option to add those symbols to load during debug time. Verify your application path and process the name and finally, click on 'debug managed memory' to check all object types which are not released with their roots.
 
 
 
You should see a screen like below which shows all the object type references with a number of hits with their roots. Using this approach, you can find all objects whose references are still not released using the VS IDE.
 
 
You could see the streamwriter's reference count which is holed by Company root object under Path to Root tab. You could find this approach a very easy and fast way to detect memory leak in a .NET application.
 
To fix this memory leak for stream writer objects, you can implement the IDisposable pattern to dispose of all unmanaged objects. Do not forget another instance of leak i.e. unsubscribe event company.OnNewJoinne -=........ it was also mentioned in a code comment. 
  1. public class Company<T> : IDisposable where T : Employee  
  2.     {  
  3.         public void Dispose()  
  4.         {  
  5.             Dispose(true);  
  6.             GC.SuppressFinalize(this);  
  7.         }  
  8.   
  9.         // Protected implementation of Dispose pattern.  
  10.         protected virtual void Dispose(bool disposing)  
  11.         {  
  12.             if (disposed)  
  13.             {  
  14.                 return;  
  15.             }  
  16.   
  17.             if (disposing)  
  18.             {  
  19.                 FileStream?.Dispose();  
  20.             }  
  21.   
  22.             disposed = true;  
  23.         }  
  24.     }  
Ideally, we should never have a memory leak in our application but sometimes you never know how these third party APIs behave. Or sometimes, we do not know how to consume those APIs in the right way which ends up in memory leak.
 
I hope you liked this blog which can be useful for your .net application to find out memory leak if any.