.NET 内存问题加载约 40 个图像,内存未回收,可能是由于 LOH 碎片 [英] .NET Memory issues loading ~40 images, memory not reclaimed, potentially due to LOH fragmentation

查看:20
本文介绍了.NET 内存问题加载约 40 个图像,内存未回收,可能是由于 LOH 碎片的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好吧,这是我第一次尝试对 .NET 应用程序进行内存分析(我已经完成了 CPU 调整),但我在这里遇到了一些障碍.

Well, this is my first foray into memory profiling a .NET app (CPU tuning I have done) and I am hitting a bit of a wall here.

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

I have a view in my app which loads 40 images (max) per page, each running about ~3MB. The max number of pages is 10. Seeing as I don't want to keep 400 images or 1.2GB in memory at once, I set each image to null when the page is changed.

现在,起初我认为我必须对这些图像有过时的引用.我下载了 ANTS profiler(顺便说一句很棒的工具)并运行了一些测试.对象生命周期图告诉我,除了父类中的单个引用之外,我没有对这些图像的任何引用(这是设计使然,也通过仔细梳理我的代码证实了这一点):

Now, at first I thought that I must just have stale references to these images. I downloaded ANTS profiler (great tool BTW) and ran a few tests. The object lifetime graph tells me that I don't have any references to these images other than the single reference in the parent class (which is by design, also confirmed by meticulously combing through my code):

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

The parent class SlideViewModelBase sticks around forever in a cache, but the MacroImage property is set to null when the page is changed. I don't see any indication that these objects should be kept around longer than expected.

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

I next took a look at the large object heap and memory usage in general. After looking at three pages of images I have 691.9MB of unmanaged memory allocated and 442.3MB on the LOH. System.Byte[], which comes from my System.Drawing.Bitmap to BitmapImage conversion is taking pretty much all of the LOH space. Here is my conversion code:

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 没有显示它们一直存在,我还进行了一个测试,我绝对确保所有这些对象都被处理掉了,但它没有有所作为.所以我还没有弄清楚所有这些非托管内存是从哪里来的.

I am having a hard time finding where all of that unmanaged memory is going. I suspected the System.Drawing.Bitmap objects at first, but ANTS doesn't show them sticking around, and I also ran a test where I made absolutely sure that all of them were disposed and it didn't make a difference. So I haven't yet figured out where all of that unmanaged memory is coming from.

我目前的两个理论是:

  1. LOH 碎片化.如果我离开分页视图并单击几个按钮,大约会回收大约 1.5GB 的一半.仍然太多,但仍然很有趣.
  2. 一些奇怪的 WPF 绑定东西.我们确实使用数据绑定来显示这些图像,而且我不是这些 WPF 控件工作原理的来龙去脉的专家.

如果有人有任何理论或分析技巧,我将非常感激,因为(当然)我们的截止日期很紧,我正在努力完成最后一部分并开始工作.我想我已经被跟踪 C++ 中的内存泄漏所宠坏了……谁会想到?

If anyone has any theories or profiling tips I would be extremely grateful as (of course) we are on a tight deadline and I am scrambling a bit to get this final part done and working. I think I've been spoiled by tracking down memory leaks in C++ ... who woulda' thought?

如果您需要更多信息或希望我尝试其他东西,请询问.很抱歉这里的墙文本,我尽量保持简洁.

If you need more info or would like me to try something else please ask. Sorry about the wall-o-text here, I tried to keep it as concise as possible.

推荐答案

这篇博文似乎描述了您所看到的内容,建议的解决方案是创建一个 实现 Stream 来包装另一个流.

This blog post appears to descibe what you are seeing, and the proposed solution was to create an implementation of Stream that wraps another stream.

这个包装类的Dispose方法需要释放被包装的流,这样才能进行垃圾回收.一旦使用此包装流初始化 BitmapImage,就可以处置包装流,释放底层流,并允许释放大字节数组本身.

The Dispose method of this wrapper class needs to release the wrapped stream, so that it can be garbage collected. Once the BitmapImage is initialised with this wrapper stream, the wrapper stream can be disposed, releasing the underlying stream, and allowing the large byte array itself to be freed.

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

The BitmapImage keeps a reference to the source stream so it keeps the MemoryStream object alive. Unfortunately, even though MemoryStream.Dispose has been invoked, it doesn't release the byte array that the memory stream wraps. So, in this case, bitmap is referencing stream, which is referencing buffer, which may be taking up a lot of space on the large object heap. There isn't a true memory leak; when there are no more references to bitmap, all these objects will (eventually) be garbage collected. But since bitmap has already made its own private copy of the image (for rendering), it seems rather wasteful to have the now-unnecessary original copy of the bitmap still in memory.

另外,您使用的是什么版本的 .NET?在 .NET 3.5 SP1 之前,存在一个已知问题,其中 BitmapImage 可能导致内存泄漏.解决方法是在 BitmapImage 上调用 Freeze.

Also, what version of .NET are you using? Prior to .NET 3.5 SP1, there was a known issue where a BitmapImage could cause a memory leak. The workaround was to call Freeze on the BitmapImage.

这篇关于.NET 内存问题加载约 40 个图像,内存未回收,可能是由于 LOH 碎片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆