C# 位图垃圾收集缓慢

Bon*_*Lee 1 c# garbage-collection bitmap

我构建控制台应用程序来进行这样的测试。

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

运行后,内存在很短的时间内增加到2GB以上。

这是 案例的日志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%
Run Code Online (Sandbox Code Playgroud)

如果我像这样改成Bitmap数组byte,内存使用量是稳定的。

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

这是数组情况的日志byte

[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%
Run Code Online (Sandbox Code Playgroud)

我知道如果我调用bmp.Dispose()它就会变得稳定的内存使用。

我很好奇为什么Bitmap垃圾收集速度很慢,为什么不是byte数组。

Han*_*ant 6

Bitmap 是一个名为 gdiplus 的非托管图形库的包装类。它是根据该库的C++ 包装器建模的,正如您从文档中可以看出的那样,C# 程序员使其接近一对一匹配。方法和属性非常小,它们只是调用库函数。

并且该类对象非常小,它没有自己的字段,并且从其基类Image继承了3个字段。仅需要 24 字节的 GC 堆。在触发垃圾收集之前,您可以创建很多,接近十万个。实际存储都是非托管的,在 gdiplus 库内,与宽度 * 高度成比例,具体取决于图像的像素格式。

仅当您调用 Dispose() 或垃圾收集器运行终结器时,才会释放非托管存储。.NET 程序员在第一次开始编程时几乎总是忽略 Dispose/using。他们总是在 Bitmap 见到制作者。

项目 > 属性 > 资源在这个故事中值得注意。它提供了非常方便的语法来在程序中使用位图资源。但这很危险,几乎没有人意识到每次在代码中使用 Properties.Resources.Somename 时,他们都会得到一个必须处理的全新 Bitmap 对象。

这种错误经常发生,以至于 Microsoft 决定在 WPF 中采用完全不同的方法。仍然是非托管图形库的包装类,更新的一个称为 WIC。但故意实现 IDisposable。以及自动处理它的方案。.NET 框架代码调用 GC.Collect(),哎呀。但更难发现它的效果并不好。