当我不将 .Save() 的位图 .Dispose() 保存到 MemoryStream 时,为什么会出现内存泄漏?
假设我创建了一个位图,
Bitmap bitmap = new Bitmap(320, 200);
当我将其写入某个流时(在我的例子中,它是一个 HttpResponseStream,由 HttpListenerResponse 给出),一切都很好:
bitmap.Save(stream, ImageFormat.Png);
我不需要 bitmap.Dispose(),即位图使用的资源会自动清理。然而,直接将 Png 写入不可查找流的问题是,它可能会导致 GDI+ 中发生一般错误,发生了当我在 Azure 上尝试我的 Asp 应用程序时。这就是我的代码现在的样子:
using (MemoryStream ms = new MemoryStream())
{
bitmap.Save(ms, ImageFormat.Png);
ms.WriteTo(stream);
}
现在除非我之后使用 bitmap.Dispose() ,否则这将会泄漏。
重新表述问题以获得更具体的答案: 为什么位图内存的泄漏似乎取决于我将其保存到的流类型?
更新: 正如我在评论中被问到是否确定这是泄漏一样。在压力测试中重复调用上述内容,我的 w3wp 进程将使用大量内存,直到我的机器开始交换并且不会清理。
Say I create a Bitmap
Bitmap bitmap = new Bitmap(320, 200);
When I write it to some stream (in my case, it's a HttpResponseStream, as given out by HttpListenerResponse), everything is fine:
bitmap.Save(stream, ImageFormat.Png);
I don't need to bitmap.Dispose(), the resources used by the bitmap will get cleaned up automatically. The problem with directly writing a Png to a non-seekable stream however is that it might result in A generic error occurred in GDI+, which happened to me when I tried my Asp app on Azure. So this is how my code looks now:
using (MemoryStream ms = new MemoryStream())
{
bitmap.Save(ms, ImageFormat.Png);
ms.WriteTo(stream);
}
Now unless I bitmap.Dispose() afterwards, this will leak.
Rephrased question to get more specific answers:
Why does this leaking of Bitmap memory seem to depend on what type of stream I save it to?
Update:
As I've been asked in comments if I'm sure it's a leak. Calling the above repeatedly in a stress test, my w3wp process will go up to gigs and gigs of memory used until my machine start swapping and it will not clean up.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
位图类利用非托管资源。这些资源与内存流类使用的资源无关。您只需将位图类包装在 using 语句中即可在使用完位图实例后对其进行处理。
错过了你问题的后半部分。 “设置它并忘记它”的一种方法是创建一个包装类,该类公开位图实例,但实现一个处理位图实例的析构函数。此析构函数意味着位图类在垃圾收集时被隐式处置。
最后一点:您实例化的任何实现 IDisposable 的对象都必须由您的代码处理。 Dipose 永远不会被隐式调用。即使在你的第一个例子中。仅仅因为将数据保存到流中并不意味着内存已被释放。大多数时候,在实例化对象的同一代码段中处理对象是一个好主意。这有助于通过提高代码透明度来更轻松地阅读代码。
Bitmap class utilizes unmanaged resources. These resources are not related to the resources utilized by the memory stream class. You can just wrap the bitmap class in a using statement to dispose of the bitmap instance when you are finished with it.
Missed the latter part of your question. One way to "set it and forget it" is by creating a wrapper class that exposes the bitmap instance but implements a destructor that disposes of the bitmap instance. This destructor will mean the bitmap class is disposed of implicitly at garbage collection.
As a final note: Any object you instantiate that implements IDisposable MUST be disposed of by your code. Dipose will never be implicitly called. Even in your first example. Just because you save the data to a stream does not meam that memory has then been deallocated. Most of the time it is a good idea to dipose of an object within the same segment of code that instantiated it. This assists in easier to read code by boosting code transparency.
我认为问题在于 GC 会神奇地清理你的对象的假设。但是,它可能永远不会这样做,我认为可能会发生以下情况:
位图使用非托管资源来保存位图数据,并且位图数据很大。因此,您将为每个位图分配一个微小托管内存块和一个巨大的非托管内存块。
因此,您可以将位图留在周围,以便 GC 闲暇时进行收集。这对于很多对象都很有效,因为很快就会有足够的内存压力,GC 会收集它们以重新使用内存。但是GC查看托管堆并说“通过处置未使用的对象,我只能恢复64字节内存。我不会打扰”。它看不到千兆字节的非托管资源,只看到堆上的几个字节。
因此您需要自己跟踪和处理位图。
有时您可能会看到它为您清理干净。这是因为在某些情况下(例如,当您处理其他对象(例如具有较大内存占用的流)时,或者只是因为这是星期二下午),它确实选择处理未使用的内存块,并且那么你的位图终于被处理了。但你不能依赖这种情况的发生。
...Ramble:
过去,指针存在两个问题。
所以在.net中他们将“指针”重命名为“引用”,添加了GC并假装问题已经不存在了。除了引用仍然可以为空之外,程序员仍然必须跟踪和管理他们的资源以避免泄漏 - 只是频率要少一些。我认为这是一件坏事 - 它让我们变得懒惰和低效,而没有真正消除根本问题,所以它会回来咬我们,我们最终会编写大量的 Dispose 逻辑,而我们过去只有一个简单的“删除”在我们的析构函数中。
I think the issue is the assumption that the GC will magically clean up your objects. However, it may never do so, and here's what I think may be happening:
Bitmaps use unmanaged resources to hold the bitmap data, and bitmap data is big. So you will be allocating a tiny block of managed memory and a huge block of unmanaged memory for each bitmap.
So you leave your bitmap lying around for the GC to collect at its leisure. This works well for a lot of objects because soon there is enough memory pressure that the GC collects them to re-use the memory. BUt the GC looks at the managed heap and says "By disposing the uniused objects, I can only recover 64 bytes of memory. I won't bother". It doesn't see the gigabytes of unmanaged resources, just the few bytes on its heap.
So you need to track and dispose of the bitmaps yourself.
It is possible that sometimes you have seen it clean up for you. This will be because under sme circumstances (such as when you are disposing other objects like streams with larger memory footprints, or just because it's a tuesday afternoon) it does choose to process the unused blocks of memory, and then your bitmap is disposed at last. But you cannot rely on this happening.
...Ramble:
There were two problems with pointers in the olden days.
So in .net they renamed "pointer" to "reference", added the GC and pretended that the problem didn't exist any more. Except that references can still be null, and programmers do still have to track and manage their resources to avoid leaks - just a little bit less often. I think this is a bad thing - it makes us lazy and inefficient without actually eliminating the underlying problem, so then it comes back and bites us, and we end up writing reams of Dispose logic where we used to just have a simple 'delete' in our destructors.
您必须处理位图才能释放 GDI+ 资源。就是这么简单。这是需要调用 Dispose 的少数几次之一。如果您正在缓存位图以减少磁盘访问,则克隆图像并使用克隆将其保存到流中。我强烈建议冲洗、关闭和处置流。完成后,将克隆和流变量设置为 null。
You must dispose the bitmap in order to release the GDI+ resources. It is that simple. It is one of the few times calling Dispose is required. If you are caching your bitmap to reduce disk access then clone the image and use the clone to save to the stream. I strongly recommend flushing, closing, and disposing of stream. When done, set clone and stream variables to null.