question

IgorBuchelnikov avatar image
0 Votes"
IgorBuchelnikov asked PeterFleischer-3316 edited

Memory leak. Class with finalizer

Created a minimal reproducing application.

Reproducing conditions:

  1. Visual Studio 2017

  2. WPF application

  3. Launch the application under the debugger

  4. WPF window code (class with finalizer, very busy WPF main thread):


code:

 using System.Windows;
    
 namespace ReproduceFinalizerMemoryLeak
 {
     public partial class MainWindow : Window
     {
         public MainWindow()
         {
             do
             {
                 Item item = new Item();
             } while (true);
    
             InitializeComponent();
         }
     }
    
     public class Item
     {
         private byte[] _bytes;
    
         public Item()
         {
             _bytes = new byte[10000];
         }
    
         ~Item()
         {
    
         }
     }
 }

What comes up:
This application crashes with OutOfMemoryException. Item class finalizer is not called. Instances of Item class are not unloaded from memory and stays in f-reachable queue.

In my real WPF application, a memory leak occurs without debugging and under other conditions, but the manifestations are the same: finalizers are not called, memory leaks. See details and memory profiling screenshots here.

There are no problems with garbage collection if

  • I run code above in console application

  • or I run code above with class without finalizer (in my real WPF application or sample application)

It seems that under certain conditions WPF main thread blocks the thread in which finalizers are called.











windows-wpf
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi, It's not a good idea to block the constructor with an infinite cycle. Build the code so that such complex operations are performed asynchronously and outside the constructor. Then the destructor is called.

0 Votes 0 ·

See answer to @vb2ae


0 Votes 0 ·

1 Answer

vb2ae avatar image
0 Votes"
vb2ae answered PeterFleischer-3316 edited

Not sure how your form will ever load with a constructor like that. You need to allow the system a chance to do some house keeping. Adding an await Task.Delay should allow the garbage collector to run


     public MainWindow()
     {
         do
         {
             Item item = new Item();
             Task.Run(async () => { await Task.Delay(10); });
             Task.WaitAll();
         } while (true);

         InitializeComponent();
     }
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

  1. It is a model (not real) code. I created it only to demonstrate how very busy WPF main thread blocks finalizers thread. My real application does not contain infinite loops in constructors but has a similar memory leak.

  2. If you run following console app

code:

 namespace ConsoleAppFinalizer
 {
     class Program
     {
         static void Main(string[] args)
         {
             do
             {
                 Item item = new Item();
             } while (true);    
         }
     }
    
     public class Item
     {
         private byte[] _bytes;
    
         public Item()
         {
             _bytes = new byte[500];
         }
    
         ~Item()
         {
    
         }
     }
 }


You will not see any problems with garbage collection.
I believe that the lesser evil is slowing down the WPF user interface than crashing with OutOfMemoryException.
Most likely, under certain conditions, WPF gets too high a priority over the f-reachable queue of the garbage collector. May be configuration settings exist?

0 Votes 0 ·

Hi Igor, please, work asynchronously!

 using System.Diagnostics;
 using System.Threading.Tasks;
 using System.Windows;
    
 namespace ReproduceFinalizerMemoryLeak
 {
   /// <summary>
   /// Interaction logic for MainWindow.xaml
   /// </summary>
   public partial class MainWindow : Window
   {
     public MainWindow()
     {
       Task.Factory.StartNew(() =>
       {
         do
         {
           Item item = new Item();
         } while (true);
       });
       InitializeComponent();
     }
   }
    
   public class Item
   {
     private byte[] _bytes;
    
     public Item()
     {
       _bytes = new byte[10000];
     }
    
     ~Item()
     {
       Debug.Print("Destructor");
     }
   }
 }
0 Votes 0 ·