Bongho Lee
Bongho Lee

Reputation: 81

C# Bitmap garbage collected slowly

I build Console application for testing like this.

while (true)
{
    var bmp = new Bitmap(1600, 1200);
    //var buffer = new byte[2000 * 1000 * 4];
    Thread.Sleep(10);
}

After running, memory is increased up to over 2GB in very short time.

This is the log for the case of Bitmap.

[2017-04-27 12:15:03][00:00:01.0150128] PrivateBytes : 1583.0MB, AllHeapsBytes : 0.0MB, Thread Count : 23, CPU Usage : 12%
[2017-04-27 12:15:04][00:00:02.0019966] PrivateBytes : 2150.0MB, AllHeapsBytes : 0.0MB, Thread Count : 24, CPU Usage : 10%
[2017-04-27 12:15:05][00:00:03.0030021] PrivateBytes : 500.0MB, AllHeapsBytes : 4.1MB, Thread Count : 24, CPU Usage : 26%
[2017-04-27 12:15:06][00:00:04.0040047] PrivateBytes : 1043.0MB, AllHeapsBytes : 4.1MB, Thread Count : 24, CPU Usage : 9%
[2017-04-27 12:15:07][00:00:05.0050024] PrivateBytes : 1601.0MB, AllHeapsBytes : 4.1MB, Thread Count : 24, CPU Usage : 7%
[2017-04-27 12:15:08][00:00:06.0060058] PrivateBytes : 2136.0MB, AllHeapsBytes : 4.1MB, Thread Count : 24, CPU Usage : 9%
[2017-04-27 12:15:09][00:00:07.0069981] PrivateBytes : 2695.0MB, AllHeapsBytes : 4.1MB, Thread Count : 24, CPU Usage : 14%

If I change Bitmap to byte array like this, the memory usage is stable.

while (true)
{
    //var bmp = new Bitmap(1600, 1200);
    var buffer = new byte[2000 * 1000 * 4];
    Thread.Sleep(10);
}

This is the log for the case of byte array.

[2017-04-27 12:25:09][00:00:01.0080196] PrivateBytes : 63.0MB, AllHeapsBytes : 31.0MB, Thread Count : 23, CPU Usage : 11%
[2017-04-27 12:25:10][00:00:02.0020012] PrivateBytes : 66.0MB, AllHeapsBytes : 46.2MB, Thread Count : 24, CPU Usage : 18%
[2017-04-27 12:25:11][00:00:03.0030496] PrivateBytes : 67.0MB, AllHeapsBytes : 47.1MB, Thread Count : 24, CPU Usage : 8%
[2017-04-27 12:25:12][00:00:04.0040530] PrivateBytes : 67.0MB, AllHeapsBytes : 47.1MB, Thread Count : 24, CPU Usage : 10%
[2017-04-27 12:25:13][00:00:05.0050386] PrivateBytes : 67.0MB, AllHeapsBytes : 47.1MB, Thread Count : 24, CPU Usage : 11%
[2017-04-27 12:25:14][00:00:06.0060466] PrivateBytes : 52.0MB, AllHeapsBytes : 31.9MB, Thread Count : 24, CPU Usage : 10%
[2017-04-27 12:25:15][00:00:07.0070521] PrivateBytes : 67.0MB, AllHeapsBytes : 47.1MB, Thread Count : 24, CPU Usage : 18%

I know that if I call bmp.Dispose() it becomes stable memory usage.

I'm curious why Bitmap is garbage-collected slowly and why not byte array.

Upvotes: 1

Views: 1131

Answers (4)

loneshark99
loneshark99

Reputation: 714

You should be using the using to dispose the Bitmap after it is used. This will give a good idea about why it is not collected as quickly.

"The .NET object representing a Bitmap is only 48 bytes, but the associated unmanaged size may be significantly larger."

http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/solving-memory-problems/understanding-and-troubleshooting-unmanaged-memory

while (true)
{
    using (var bmp = new Bitmap(1600, 1200))
    {
        // var buffer = new byte[2000 * 1000 * 4];
        Thread.Sleep(10);
    }
}

Upvotes: 0

Hans Passant
Hans Passant

Reputation: 941407

Bitmap is a wrapper class for an unmanaged graphics library called gdiplus. It is modeled after the C++ wrapper for that library, the C# programmer made it close to a one-to-one match as you can tell from the documentation. The methods and properties are very small, they just pinvoke the library functions.

And the class object is very small, it has no fields of its own and inherits 3 from its base class Image. Just 24 bytes of GC heap required. You can create a lot of them before you'll trigger a garbage collection, close to a hundred thousand. Real storage is all unmanaged, inside the gdiplus library, proportional to Width * Height, depending on the pixel format of the image.

Releasing that unmanaged storage only happens when you call Dispose() or when the garbage collector runs the finalizer. .NET programmers almost always ignore Dispose/using when they first get started programming it. They invariably meet the maker at Bitmap.

Project > Properties > Resources is notable in this story. It provides very convenient syntax to use a bitmap resource in your program. But it is dangerous, just about nobody realizes that every time they use Properties.Resources.Somename in their code, they get a brand-new Bitmap object that must be disposed.

This went wrong so often that Microsoft decided for a completely different approach in WPF. Still a wrapper class for an unmanaged graphics library, a more recent one called WIC. But intentionally not implementing IDisposable. And a scheme to dispose it automatically. .NET framework code calling GC.Collect(), ouch. But much harder to find out that it didn't pan out well.

Upvotes: 6

Slava
Slava

Reputation: 1095

Bitmap is a finalizable object. It has unmanaged resources that need to be disposed properly. These resources are usually disposed when you call Dispose() method. If Dispose() was not called than CLR needs to dispose the resources by calling Bitmaps's finalizer first. This is done by a specific thread in CLR that is called finalization thread. This is an additional step that takes time. This is why you always need to Dispose() bitmap if you do not need it. using statement does it automatically. Byte array is a simple managed object. It does not require finalization

Upvotes: 1

Vijunav Vastivch
Vijunav Vastivch

Reputation: 4191

Because Bitmap consider as an image it is a big difference between Image and byte.

Upvotes: 0

Related Questions