GDI+内存泄漏主因是Bitmap和Graphics未显式Dispose,导致非托管资源累积;正确做法是用嵌套using语句确保Bitmap在外层、Graphics在内层释放,并同步释放Pen/Brush等基元对象及Win32句柄。

Bitmap 和 Graphics 不 Dispose 就会内存暴涨
不调用 Dispose() 是 GDI+ 内存泄漏最常见、最直接的原因。哪怕只是循环截图 100 次,每次创建 Bitmap 和 Graphics 后不释放,就可能在非托管堆上累积数 MB 甚至上百 MB 的“僵尸图像数据”。GC 不会立即回收它们,终结器执行时机不可控,高并发下极易触发 OutOfMemoryException。
典型错误写法:
Bitmap bmp = new Bitmap(1920, 1080); Graphics g = Graphics.FromImage(bmp); g.CopyFromScreen(...); // 忘记 g.Dispose() 和 bmp.Dispose() // 变量离开作用域 ≠ 资源被释放
-
Bitmap和Graphics都实现了IDisposable,必须显式释放 - 仅靠
null赋值或让变量出作用域,无法触发非托管资源清理 - 使用
using是最安全、最推荐的方式,异常时也能保证释放
用 using 语句替代手动 Dispose(但要注意嵌套顺序)
using 不是万能的语法糖——嵌套顺序错了,照样泄漏。比如先 using Graphics,再 using Bitmap,而 Graphics 又依赖 Bitmap,那 Graphics 析构时 Bitmap 可能已被释放,导致 GDI 句柄失效或访问冲突。
正确嵌套方式(外层生命周期长于内层):
using (Bitmap bmp = new Bitmap(width, height))
{
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(...);
// 其他绘图操作
} // g.Dispose() 在这里执行
} // bmp.Dispose() 在这里执行
- 总是把被依赖的对象(如
Bitmap)放在外层using - 避免跨作用域传递
Graphics或Bitmap实例,尤其不要返回未Dispose的对象 - 若需复用,应封装为静态/缓存对象,并确保线程安全
从 HBITMAP 创建 Bitmap 时容易漏掉 Win32 资源释放
用 Bitmap.FromHbitmap(hbitmap) 创建托管 Bitmap,并不自动释放原始 HBITMAP 句柄。很多代码在调用 Win32API.CreateCompatibleBitmap 后,只释放了 Bitmap,却忘了 Win32API.DeleteObject(hbitmap),造成 GDI 句柄持续增长,最终触发系统级句柄耗尽(错误常表现为界面卡顿、绘图失败,而非直接抛异常)。
-
Bitmap.FromHbitmap()返回的对象和原始HBITMAP是独立生命周期 - 必须在
Bitmap使用完毕后,显式调用Win32API.DeleteObject(hbitmap) - 如果后续还要用该
Bitmap,不能提前删hbitmap;建议用Marshal.Release或封装句柄管理逻辑 - 别依赖
GC.Collect()强制回收——它治标不治本,且影响性能
Pen / Brush / Font 等 GDI 基元对象也要 Dispose
很多人记得释放 Bitmap 和 Graphics,却忽略 Pen、Brush、Font 这些看似轻量的对象。它们内部都持有 GDI 句柄,长期不释放一样会导致句柄泄漏(Windows 单进程默认 GDI 句柄上限约 10000,超限后新绘图调用会静默失败)。
安全写法示例:
using (Pen pen = new Pen(Color.Red, 2))
using (SolidBrush brush = new SolidBrush(Color.Blue))
{
g.DrawRectangle(pen, rect);
g.FillRectangle(brush, rect);
}
- 所有实现
IDisposable的 GDI+ 类型,都应进using或配对Dispose() - 避免在循环中反复 new
Pen——可复用静态实例(如Pens.Red),但注意其线程安全性 - 自定义
Brush(如LinearGradientBrush)必须释放,系统预定义的(如Brushes.Black)不用
Graphics 实例,它们很难被统一管控,必须从设计源头约束创建与释放边界。


















