StayOnTarget
StayOnTarget

Reputation: 13007

Window object not released after being closed, even after GC Collect and WaitForPendingFinalizers?

This is a simple test application to help understand WPF memory usage. They key thing I want to understand is why MainWindow is still referenced and it's memory not released, even after being closed and having waited for GC finalization?


(See code listing below)

The text "MainWindow finalizer" is not executed by the time of snapshot #2, which seems unexpected. To investigate, I have taken two memory snapshots using VS diagnostic tools at the points indicated in the code listing.

Here's the VS comparison of the two snapshots:

enter image description here

This shows that the MainWindow is still around. But why, if nothing is referencing it? Drilling down (again using the diagnostic tools) it turns out that there is a reference after all:

enter image description here

There are other objects also referencing the MainWindow, but they all eventually form a cycle back to it, so I do not think they are genuinely "root" objects which are keeping the reference alive. But for the MediaContext / Dispatcher duo this is not the case.

The Dispatcher as I understand it is run once per thread so that seems OK by itself. But what's up with the MediaContext that it owns, which in turns holds onto my MainWindow?

Is this normal? Is it a "memory leak"? Why does it happen?

Also, importantly how can I / should I actually get rid of the MainWindow object?


App.xaml:

<Application
    x:Class="memtest.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:memtest"
    StartupUri="MainWindow.xaml"
    Startup="Application_Startup"
    >
    <Application.Resources/>
</Application>

App.xaml.cs:

namespace memtest
{
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // *** SNAPSHOT 1 ***

            ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;

            MainWindow window = new MainWindow();
            window.Show();
            window.Close();
            window = null;

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

            // *** SNAPSHOT 2 ***
        }
    }
}

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Debug.WriteLine("MainWindow constructor");
    }

    ~MainWindow()
    {
        // Never reached
        Debug.WriteLine("MainWindow finalizer");
    }
}

MainWindow.XAML is the default created by VS, which contains only an empty grid.

There is no other code in the project.

This is a .NET 4.72 project.


This is not quite a dupe of A WPF window doesn't release the memory after closed, because it did not use WaitForPendingFinalizers() nor did it use an explicit finalizer. And that question has no valid answers.

Upvotes: 0

Views: 948

Answers (1)

Zarat
Zarat

Reputation: 2754

This test is making several mistakes, also explained here

  • you are taking the second snapshot while the MainWindow variable is still on the stack frame. JIT is allowed to optimize your assignment to window = null; away because it can clearly see the variable is not used anymore afterwards. Furthermore GC reporting of stack frames is not exact (with respect to your source), there may be hidden copies on the stack. Move the test code into a separate method which you return from, to make sure no references to MainWindow are left on the stack. (Technically not necessary after fixing the next point, but I'm mentioning it for completeness so people understand that point when writing GC tests.)
  • you are not giving the multithreaded WPF rendering engine time to clean up, closing and forcing GC is not enough to synchronize with the rendering engine to clean up its resources
  • you are leaving StartupUri="MainWindow.xaml" in the app, remove it to make testing with the fixed code simpler

The correct way to perform your test is to start a DispatcherTimer and take the second snapshot there, for me the MainWindow is gone then.

private void Application_Startup(object sender, StartupEventArgs e)
{
    // *** SNAPSHOT 1 ***

    ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;

    RunTest();

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

    new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, Callback, Dispatcher).Start();
}

private void Callback(object sender, EventArgs e)
{
    // *** SNAPSHOT 2 ***
}

private static void RunTest()
{
    MainWindow window = new MainWindow();
    window.Show();
    window.Close();
    window = null;
}

Upvotes: 1

Related Questions