.NET内存问题加载~40张图像,内存未回收,可能是由于LOH碎片造成的

时间:2011-06-07 21:32:43

标签: c# .net wpf memory-management memory-leaks

嗯,这是我第一次尝试内存分析.NET应用程序(我做过的CPU调优),我在这里遇到了一些问题。

我的应用程序中有一个视图,每页加载40张图像(最大),每张图像大约运行3MB。最大页数为10.由于我不想一次保留400张图像或1.2GB内存,因此我在页面更改时将每个图像设置为空。

现在,起初我认为我必须对这些图像进行过时的引用。我下载了ANTS profiler(伟大的工具BTW)并进行了一些测试。对象生命周期图表告诉我除了父类中的单个引用之外,我没有对这些图像的任何引用(这是设计的,也通过我的代码精心梳理确认):

enter image description here

父类SlideViewModelBase永远存在于缓存中,但更改页面时MacroImage属性设置为null。我没有看到任何迹象表明这些物体应该保持比预期更长的时间。

我接下来看一下大对象堆和内存使用情况。在查看三页图像后,我分配了691.9MB的非托管内存和LOH上的442.3MB。来自我的System.Byte[]System.Drawing.Bitmap转换的BitmapImage几乎占据了所有LOH空间。这是我的转换代码:

public static BitmapSource ToBmpSrc( this Bitmap b )
{
    var bi = new BitmapImage();
    var ms = new MemoryStream();
    bi.CacheOption = BitmapCacheOption.OnLoad;
    b.Save( ms,  ImageFormat.Bmp );
    ms.Position = 0;
    bi.BeginInit();
    ms.Seek( 0, SeekOrigin.Begin );
    bi.StreamSource = ms;
    bi.EndInit();
    return bi;
}

我很难找到所有非托管内存的去向。我起初怀疑是System.Drawing.Bitmap个物体,但是ANTS并没有显示它们四处徘徊,而且我还进行了一次测试,我确保所有这些都被处理掉了,并没有什么区别。所以我还没弄清楚所有非托管内存的来源。

我目前的两个理论是:

  1. LOH碎裂。如果我离开分页视图并单击几个按钮,大约一半的~1.5GB被回收。仍然太多,但仍然很有趣。
  2. 一些奇怪的WPF绑定的东西。我们确实使用数据绑定来显示这些图像,而且我对这些WPF控件如何工作的细节不是专家。
  3. 如果有人有任何理论或剖析提示,我将非常感激,因为(当然)我们正处于紧迫的截止日期之前,我正在努力争取最后一部分完成和工作。我认为通过追踪C ++中的内存泄漏我已经被宠坏了......谁会想到?

    如果您需要更多信息或希望我尝试别的,请询问。对于这里的墙上文字感到抱歉,我尽量保持简洁。

2 个答案:

答案 0 :(得分:35)

blog post似乎描述了您所看到的内容,建议的解决方案是创建implementation of Stream that wraps another stream

此包装类的Dispose方法需要释放包装的流,以便可以对其进行垃圾回收。使用此包装器流初始化BitmapImage后,可以释放包装器流,释放基础流,并允许释放大字节数组本身。

  

BitmapImage保留对源流的引用,以使MemoryStream对象保持活动状态。不幸的是,即使已经调用了MemoryStream.Dispose,它也不会释放内存流包装的字节数组。因此,在这种情况下,位图是引用流,它引用缓冲区,这可能占用大对象堆上的大量空间。没有真正的内存泄漏;当没有更多对位图的引用时,所有这些对象将(最终)被垃圾收集。但由于位图已经制作了自己的图像私有副本(用于渲染),因此将位图的现在不必要的原始副本保留在内存中似乎相当浪费。

此外,您使用的是什么版本的.NET?在.NET 3.5 SP1之前,存在BitmapImage could cause a memory leak的已知问题。解决方法是在BitmapImage上调用Freeze

答案 1 :(得分:2)

你在哪里关闭和处理内存流?可能是GC必须更加努力地通过在对象上执行析构函数之前移动几代来释放资源(如果你忘了这样做,通常会调用dispose)。

在您的情况下,在完成图像之前,您无法处理内存流。当你想要它们被卸载时,循环遍历图像并尝试处理内存流。