4

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.

Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156
  • Are you sure it's a memory leak and not just that the garbage collection hasn't cleaned it up yet? – Matthew Jan 12 '12 at 06:01
  • Are you sure you have a memory leak? They re-defined the definition of what a memory leak is with the invention of .Net ;) – James Jan 12 '12 at 06:04
  • @James; with GDI+ there can be system memory leaks, which will be cleaned up once the AppDomain is released. – AMissico Jan 12 '12 at 07:47

3 Answers3

5

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.

doogle
  • 3,376
  • 18
  • 23
  • 1
    The Bitmap gets or does not get cleaned up without me calling bitmap.Dispose(), depending only on the (type of) Stream I .Save() it to. That's what's puzzling to me. And btw, Bitmap.Dispose() does actually get called implicitly, the ~Image() destructor does it. Check the source code. – Evgeniy Berezovsky Jan 12 '12 at 07:06
2

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.

  • They could be null, leading to crashing code
  • You could forget to free their memory/resources and get leaks

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.

Jason Williams
  • 56,972
  • 11
  • 108
  • 137
0

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.

AMissico
  • 21,470
  • 7
  • 78
  • 106
  • Why is there no leak when I save to something other than a MemoryStream? I'm not calling Dispose there, and it works fine. Adding what I said in another comment: Bitmap.Dispose() does actually get called implicitly, the ~Image() destructor does it. – Evgeniy Berezovsky Jan 12 '12 at 07:55
  • What benefits does cloning give me? I don't know what cloning does, but it sounds like it would consume even more memory. (The MSDN docs only state: Creates an exact copy of this Image.). Btw., the disk is not involved in my case. These are generated images, cached to trade off cpu time against memory. – Evgeniy Berezovsky Jan 12 '12 at 08:07
  • @EugeneBeresovksy; Yes, I know that Dispose is called implicitly. I also know that .NET initializes and terminates the GDI library properly. What I am telling you is based on ten years of .NET experience, and you need to call Dispose and you need to set the variable holding the Image to null. – AMissico Jan 12 '12 at 08:25
  • @EugeneBeresovksy; Cloning keeps your cache image safe and avoids potential GDI/GDI+ errors. – AMissico Jan 12 '12 at 08:27
  • @EugeneBeresovksy; If you do not care about memory then cloning is a non-issue. Moreover, if you follow my advice, the memory consumption will insignificant. – AMissico Jan 12 '12 at 08:27
  • It is called a finializer and not "destructor". – AMissico Jan 12 '12 at 08:29
  • @EugeneBeresovksy; If this is a web application, then you need to make sure the cached image is locked while you are saving. GDI+ is not thread-safe. – AMissico Jan 12 '12 at 08:31
  • Thanks for 'make sure cached image is locked' hint - will look into it. – Evgeniy Berezovsky Jan 12 '12 at 11:50