为什么我没有内存泄漏.Dispose()我将.Save()的位图发送到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进程将进入gig和gig的内存使用,直到我的机器开始交换,它将无法清理。

Bitmap类使用非托管资源。 这些资源与内存流类使用的资源无关。 您可以将位图类包装在using语句中,以便在完成后处理位图实例。

错过了你问题的后半部分。 “设置并忘记它”的一种方法是创建一个包装类,该类公开位图实例,但实现了一个处理位图实例的析构函数。 这个析构函数意味着位图类在垃圾收集中被隐式处理。

最后要注意:实例化实现IDisposable的任何对象都必须由代码处理。 Dipose永远不会被暗中调用。 即使在你的第一个例子。 仅仅因为您将数据保存到流中并不意味着内存已被释放。 大多数情况下,在实例化它的同一代码段中对一个对象进行dipos是个好主意。 这有助于通过提高代码透明度来更轻松地阅读代码。

我认为问题在于GC会神奇地清理你的物体。 但是,它可能永远不会这样做,这就是我认为可能发生的事情:

位图使用非托管资源来保存位图数据,位图数据很大。 因此,您将为每个位图分配一小块托管内存和一大块非托管内存。

所以你留下你的位图,让GC在闲暇时收集。 这适用于很多对象,因为很快就有足够的内存压力,GC会收集它们以重新使用内存。 让GC查看托管堆并说“通过处理未使用的对象,我只能恢复64字节的内存。我不会打扰”。 它没有看到数十亿字节的非托管资源,只看到其堆上的几个字节。

因此,您需要自己跟踪和处理位图。

有时你可能已经看到它为你清理。 这将是因为在存在情况下(例如当您处理其他对象(如具有较大内存占用的流,或仅仅因为它是星期二下午)时,它选择处理未使用的内存块,然后最后处理您的位图。 但你不能依靠这种情况发生。

…漫谈:

在过去的日子里,指针存在两个问题。

  • 它们可能为null,导致代码崩溃
  • 你可能忘记释放他们的记忆/资源并泄漏

所以在.net中,他们将“指针”重命名为“引用”,添加了GC并假装问题不再存在。 除了引用仍然可以为空,程序员仍然必须跟踪和管理他们的资源以避免泄漏 – 只是少一点。 我认为这是一件坏事 – 它使我们懒惰而且效率低而没有实际消除潜在的问题,所以它又回来并咬我们,最后我们写了一些Dispose逻辑,我们曾经只是简单地’删除’在我们的析构函数中。

您必须处置位图才能释放GDI +资源。 就这么简单。 这是为数不多的需要调用Dispose的时间之一。 如果要缓存位图以减少磁盘访问,则克隆映像并使用克隆保存到流中。 我强烈建议冲洗,关闭和处理流。 完成后,将clone和stream变量设置为null。