« Managed Debugging Assistant Configuration Files | Main | Finalizers called from partially constructed objects »
April 5, 2008
“Memory leak” with BitmapImage and MemoryStream
The code snippet below has a small “memory leak”:
BitmapImage bitmap = new BitmapImage();
byte[] buffer = GetHugeByteArray(); // from some external source
using (MemoryStream stream = new MemoryStream(buffer, false))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
}
// use bitmap...
The BitmapImage keeps a reference to the source stream (presumably so that you can read the StreamSource property at any time), 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. Note that 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.
The solution here is fairly straightforward: create an implementation of Stream that wraps another stream (in this example, the MemoryStream). 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.
Posted by Bradley Grainger at April 5, 2008 12:40 PM
Trackback Pings
TrackBack URL for this entry:
http://blog.logos.com/mt-cgi/mt-tb.cgi/205
Comments
I believe this is fixed in the upcoming .NET 3.5 service release this summer.
Posted by: Rob at April 5, 2008 5:16 PM
I retested this with .NET 3.5 SP1, and the problem I described is still there. I think it's unlikely that Microsoft could ever change the implementation of these classes to avoid the problem, because there's probably a lot of code that depends on being able to call MemoryStream.ToArray or MemoryStream.GetBuffer after the memory stream has been disposed. For example, making MemoryStream release its buffer in Dispose would break the following code:
MemoryStream stream = new MemoryStream();
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(1);
} // BinaryWriter.Dispose will dispose the stream
return stream.ToArray();
Posted by: Bradley Grainger at August 20, 2008 12:57 PM
good article, i met the same problem. Can you send me the code of your straightforward solution that is wrapper the class MemoryStream? thank you very much.:)
Posted by: Anonymous at May 6, 2009 2:33 AM
I've now blogged the implementation of this class: http://code.logos.com/blog/2009/05/wrappingstream_implementation.html
Posted by: Bradley Grainger
at May 6, 2009 10:12 AM
Thanks! Exactly what I was looking for. Very useful.
Posted by: Gustavo C at May 21, 2009 9:05 AM