即使关闭了GC Collect和WaitForPendingFinalizers,窗口对象在关闭后也不会释放?

问题描述

这是一个简单的测试应用程序,可帮助您了解WPF内存使用情况。我想了解的关键是,为什么MainWindow仍然被引用并且即使在关闭并等待GC最终完成后仍未释放内存?


(请参见下面的代码清单)

在快照#2之前,尚未执行文本“ MainWindow finalizer”,这似乎是意外的。为了进行调查,我在代码清单中指示的点使用VS诊断工具拍摄了两个内存快照。

这是两个快照的VS比较:

enter image description here

这表明MainWindow还在附近。但是为什么呢?深入研究(再次使用诊断工具),结果毕竟是一个参考:

enter image description here

还有其他对象也引用MainWindow,但是它们最终都会形成一个循环,因此我认为它们不是真正的“根”对象,它们使引用保持活动状态。但是对于MediaContext / dispatcher二人组则并非如此。

据我所知,dispatcher每个线程运行一次,这样看来就可以了。但是它拥有的MediaContext是怎么回事,而这又又保留了我的MainWindow

这正常吗?这是“内存泄漏”吗?为什么会发生?

此外,重要的是,我/应该如何摆脱MainWindow对象?


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 是VS创建的认设置,它仅包含一个空网格。

项目中没有其他代码

这是一个.NET 4.72项目。


这不是A WPF window doesn't release the memory after closed的伪装,因为它既没有使用WaitForPendingFinalizers()也没有使用显式的终结器。而且这个问题没有有效的答案。

解决方法

此测试犯了几个错误,也解释了here

  • 您正在拍摄第二张快照,而MainWindow变量仍在堆栈框架上。 JIT可以优化您对window = null;的分配,因为它可以清楚地看到以后不再使用该变量。此外,堆栈帧的GC报告不准确(相对于您的来源),堆栈上可能存在隐藏的副本。将测试代码移到您从中返回的单独方法中,以确保堆栈上没有剩余对MainWindow的引用。 (在确定下一个点后,从技术上讲是不必要的,但是出于完整性的考虑,我要提到它,以便人们在编写GC测试时理解这一点。)
  • 您没有给多线程WPF渲染引擎足够的时间来清理,关闭和强制GC不足以与渲染引擎同步来清理其资源
  • 您要将StartupUri="MainWindow.xaml"留在应用中,请将其删除以简化使用固定代码进行的测试

执行测试的正确方法是启动DispatcherTimer并在那里获取第二张快照,对我而言MainWindow消失了。

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;
}