Find, fix and prevent memory leaks in C # .NET: 8 best practices

For future students of the "C # Developer" course and all those interested, we prepared a translation of useful material.



We also invite you to participate in the
open webinar on the topic "LINQ Methods That Do Everything For You" - where participants will discuss six representatives of the LINQ family of technologies, three components of the main query operation, deferred and immediate execution, parallel queries.






Anyone who has worked on a large corporate project knows that memory leaks are like rats in a big hotel. You may not notice them when there are few of them, but you should always be on the lookout in case they breed, sneak into the kitchen and dirty everything around.





, — . 8 , .NET , . , , . , .





.NET

« » . , (GC garbage collector), ?





. — , , . , , , . , , event



.





, - ( ) . . .NET , . , , , , . Dispose



, ( ). .NET ., Marshal



PInvoke



( ).





:





1.

Debug | Windows | Show Diagnostic Tools, . -, , , Visual Studio, . . 2 : GC Pressure ( ).





, (Process Memory) :





, , , , - .





GC Pressure, :





GC Pressure — , . , , .





, , , . Visual Studio Enterprise , . .





2. , Process Explorer PerfMon

(Task Manager) Process Explorer ( SysInternals). , . , , .





PerfMon , . , , . Process | Private Bytes.





, . , . , / , (). , GC Pressure. , , .





, , . , - ( ).





3.

-. . ( ), , .





.NET: dotMemory, SciTech Memory Profiler ANTS Memory Profiler. «» , Visual Studio Enterprise.





. . . . , :





, , GC Root.





GC Root — , , , GC Root, . , , GC Roots. « .NET».





— , . , . , :





  1. - (Idle state) . - .





  2. , .





  3. , , . .





  4. .





  5. .





  6. New-Created-Instances, , . «path to GC Root» , .





, SciTech , :





4. «Make Object ID»

5 , - C# .NET, , , Finalizer. , . Make Object ID (Immediate Window).





, , . , , . , , :





  1. , .





  2. , , Make Object ID



    . Immediate $1



    , , Object ID



    .





  3. , .





  4. .





GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
      
      



5. $1



. null



, , . , .





:





, :





, , .





: .NET Core 2.X (). , , . , , .





5.

, , . , .





:





  • (Events) .NET , . , , . , : 5 , - C# .NET,





  • , , , . , GC Roots, .





  • — . , OutOfMemory. .





  • WPF . — DependencyObject INotifyPropertyChanged. , WPF ( ViewModel) , . WPF StackOverflow.





  • . , , , — . :





public class MyClass
{
    private int _wiFiChangesCounter = 0;
 
    public MyClass(WiFiManager wiFiManager)
    {
        wiFiManager.WiFiSignalChanged += (s, e) => _wiFiChangesCounter++;
    }
      
      



  • , . Live Stack GC Root. , , , . . , . :





public class MyClass
{
    public MyClass(WiFiManager wiFiManager)
    {
        Timer timer = new Timer(HandleTick);
        timer.Change(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
    }
 
    private void HandleTick(object state)
    {
        // do something
    }
      
      



8 .NET.





6. Dispose

.NET . .NET , Win32 API. , , , , , , .





.NET Framework



, , IDisposable



. , , Dispose



. — Dispose



. , using



.





public void Foo()
{
    using (var stream = new FileStream(@"C:\Temp\SomeFile.txt",
                                       FileMode.OpenOrCreate))
    {
        // do stuff
 
    }// stream.Dispose() will be called even if an exception occurs
      
      



using



try / finally



, Dispose



finally



.





Dispose



, , .NET



Dispose. , Dispose



, Finalizer



, . , Finalizer



.





, Dispose



. :





public class MyClass : IDisposable
{
    private IntPtr _bufferPtr;
    public int BUFFER_SIZE = 1024 * 1024; // 1 MB
    private bool _disposed = false;
 
    public MyClass()
    {
        _bufferPtr =  Marshal.AllocHGlobal(BUFFER_SIZE);
    }
 
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;
 
        if (disposing)
        {
            // Free any other managed objects here.
        }
 
        // Free any unmanaged objects here.
        Marshal.FreeHGlobal(_bufferPtr);
        _disposed = true;
    }
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
 
    ~MyClass()
    {
        Dispose(false);
    }
}

      
      



— . , ( Finalizer



), Dispose()



.





GC.SuppressFinalize(this)



. , Finalizer



, . Finalizer- . Finalizer



F-Reachable-Queue



, . .





7.

. , , . , - , . , , .





. :





Process currentProc = Process.GetCurrentProcess();
var bytesInUse = currentProc.PrivateMemorySize64;
      
      



PerformanceCounter



— , PerfMon



:





PerformanceCounter ctr1 = new PerformanceCounter("Process", "Private Bytes", Process.GetCurrentProcess().ProcessName);
PerformanceCounter ctr2 = new PerformanceCounter(".NET CLR Memory", "# Gen 0 Collections", Process.GetCurrentProcess().ProcessName);
PerformanceCounter ctr3 = new PerformanceCounter(".NET CLR Memory", "# Gen 1 Collections", Process.GetCurrentProcess().ProcessName);
PerformanceCounter ctr4 = new PerformanceCounter(".NET CLR Memory", "# Gen 2 Collections", Process.GetCurrentProcess().ProcessName);
PerformanceCounter ctr5 = new PerformanceCounter(".NET CLR Memory", "Gen 0 heap size", Process.GetCurrentProcess().ProcessName);
//...
Debug.WriteLine("ctr1 = " + ctr1 .NextValue());
Debug.WriteLine("ctr2 = " + ctr2 .NextValue());
Debug.WriteLine("ctr3 = " + ctr3 .NextValue());
Debug.WriteLine("ctr4 = " + ctr4 .NextValue());
Debug.WriteLine("ctr5 = " + ctr5 .NextValue());
      
      



perfMon, .





. CLR MD (Microsoft.Diagnostics.Runtime) . , , , . .





, CLR MD, DumpMiner .





, , , Application Insights.





8.  

— . . , :





[Test]
void MemoryLeakTest()
{
  var weakRef = new WeakReference(leakyObject)
  // Ryn an operation with leakyObject
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();
  Assert.IsFalse(weakRef.IsAlive);
}
      
      



, .NET Memory Profiler SciTech dotMemory, API:





MemAssertion.NoInstances(typeof(MyLeakyClass));
MemAssertion.NoNewInstances(typeof(MyLeakyClass), lastSnapshot);
MemAssertion.MaxNewInstances(typeof(Bitmap), 10);
      
      



, , , , : .





, , . .






« C#».









« LINQ, ».












All Articles