【问题标题】:Does the following XAML potentialy leak memory?以下 XAML 是否可能泄漏内存?
【发布时间】:2010-08-23 22:13:12
【问题描述】:

我正在使用 .NET4 WPF DataGrid 来显示一个包含大量图像的 SQL 表。

有问题的 XAML 代码是:

...
<DataGridTemplateColumn.CellTemplate>
   <DataTemplate>
      <Image Source="{Binding Converter={StaticResource ImageConverter}, Path=Picture}" Stretch="Uniform" MaxHeight="200" />
   </DataTemplate>
</DataGridTemplateColumn.CellTemplate>
...

ImageConverter 是这样写的:

[ValueConversion(typeof(Binary), typeof(BitmapImage))]
public class ImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null)
        {
            BitmapImage bi = new BitmapImage();
            bi.BeginInit();
            bi.StreamSource = new System.IO.MemoryStream((value as Binary).ToArray());
            bi.EndInit();
            if (bi.CanFreeze) bi.Freeze();

            return bi;
        }
        else return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

我的问题是上面的代码会不会泄露内存?


我已尝试对此进行一些分析,但我不确定我是否正确解释了结果。

首先,包含图像的 SQL 表使用 38MB 的磁盘空间(图像应以 png 格式存储)。通过 LINQ 将所有图像加载到数据网格后,该应用程序使用了大约 170+ MB 的额外 RAM。这可能有助于解压缩图像以及 wpf 数据网格即使在启用虚拟化时也是一个巨大的内存消耗这一事实。关闭窗口后,内存使用量不会下降。重新打开窗口会导致另外 170+ MB 的 RAM 被使用,使总内存使用量大约为 400MB。如果我再次重新打开窗口,那么内存使用量将下降到大约 330MB。再次重新打开窗口会使内存使用量达到 380MB。再次重新打开使其达到 270MB。再次重新打开使其达到 426MB。所以你可以看到非常波动......

这一点测试是在 Win7 x64 和 8GB RAM 下完成的(应用程序是使用 Any CPU 选项编译的)。

我确实在具有

我对这些结果的解释是,除非内存子系统上存在高压,否则 GC 不会费心清理。但是在那种情况下,在我关闭窗口后调用 GC.Collect() 应该释放大部分内存吗?只是我没有,当我调用 GC.Collect 时,我只看到内存使用量下降了 4-6MB(我还尝试了所有可能的参数,包括在所有代上强制收集和调用 GC.WaitForPendingFinalizers())。

这让我想到可能是未使用的泄漏数据被推送到页面文件中。但是页面文件使用量会根据内存使用量下降和上升。

这里考虑的所有事情都不应该是内存泄漏,但是在我关闭窗口后我无法降低内存使用量。我确实尝试将一些 .NET 分析器附加到我的进程中,但是这些分析器非常复杂,以至于我无法确定显示的图像对象是否仍然存在并被某些东西引用。还是它们已经死了,而 GC 根本没有清理它们...

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    可以通过GC.GetTotalMemory(true)查看内存是否写入页面文件。这应该返回 .NET 进程的实际内存消耗,包括可能已写入页面文件的任何页面。

    这甚至可能不同于例如任务管理器报告的内存,但它会报告 .NET 对象实际分配的内存。

    有关任务管理器和GetTotalMemory报告的内存消耗差异的更多信息,请参阅http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/2ebbff38-f881-47ad-a4b4-9c9157fb3b5b

    我的猜测是GC.GetTotalMemory(true) 会报告收集后实际上所有内存都已释放。

    【讨论】:

    • 我将把答案交给Francesco,因为他先回复了,但你的回答也很好。 GC.GetTeotalMemory 确实报告了我的应用程序使用的正常内存量,即使任务管理器显示多出 120MB。我想这与您提供的链接中所说的有关 - GC 已释放内存,但未将其释放到操作系统或类似的东西。可惜没有办法强制释放不必要的内存,我很乐意为 2-3 秒的 GC 工作付出代价。 (让我想起了很多 SQL 日志文件,你必须手动缩小它以释放硬盘空间)
    • 谢谢。太糟糕了,这里如此强调第一个答案而不是正确的答案:)。
    • 投了赞成票,因为我了解了 GC.GetTotalMemory(我不知道)并且因为我偷了你的赏金 ;-)
    • StackOverflow 不允许设置多个答案有点令人失望,因为有时一个问题的完整答案是一个以上的总和。在这种情况下,我倾向于奖励第一个答案,尽管现在考虑一下,你的答案更完整(切换答案对你有利,虽然我不知道这是否会回馈声誉)......我会下次尝试更仔细地决定;)
    【解决方案2】:

    在我看来,该代码中没有内存泄漏。虚拟机上的内存几乎立即下降的事实意味着 GC 正确地完成了它的工作。

    我发现在类似情况下有用的一件事是在某处放置一个调用 GC.Collect 的按钮,然后多次点击它。通常在大约 3 次死对象被“GCollected”之后,如果不是这种情况,您可能有泄漏。

    【讨论】:

    • 我在一个按钮中尝试了你关于 GC.Collect 的建议,得到了一些奇怪的结果。首先,我之前尝试过 GC.Collect(),就在我关闭 Window 之后没有给出任何结果(也许 Window 没有超出范围)。现在,如果我按下按钮,大约 80MB 会立即释放,但不会释放 100 左右 MB。重复重新打开并按下按钮的过程,会产生与以前相似的结果 - 内存消耗波动很大。只有在按下按钮后~80MB 会立即释放......有什么想法吗?或者是使用分析器了解更多信息的唯一选择?
    • 我们过去使用memprofiler.com 取得了很好的成功。它是免费的,而且使用起来并不难。顺便说一句,如果在 GC.Collect n 次内存被释放后,这意味着即使 n 是一个非常大的数字也没有泄漏。垃圾收集器是“懒惰的”(有充分的理由),即使有未引用的对象,它也可能决定不需要释放资源。
    【解决方案3】:

    DataGridTemplateColumn 的内存泄漏问题似乎在 .NET 4.5 中消失了

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-26
      • 1970-01-01
      • 2012-08-19
      • 1970-01-01
      • 2011-08-12
      • 2015-07-30
      • 1970-01-01
      • 2012-05-20
      相关资源
      最近更新 更多