问题描述
这是一个简单的测试应用程序,可帮助您了解WPF内存使用情况。我想了解的关键是,为什么MainWindow
仍然被引用并且即使在关闭并等待GC最终完成后仍未释放内存?
(请参见下面的代码清单)
在快照#2之前,尚未执行文本“ MainWindow finalizer”,这似乎是意外的。为了进行调查,我在代码清单中指示的点使用VS诊断工具拍摄了两个内存快照。
这是两个快照的VS比较:
这表明MainWindow
还在附近。但是为什么呢?深入研究(再次使用诊断工具),结果毕竟是一个参考:
还有其他对象也引用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;
}