在处理大量图片时,我偶然发现了OutOfMemory-Exception(顺序,不是并行)。我在代码的一小部分中重现了这种行为:
class ImageHolder
{
public Image Image;
~ImageHolder()
{
Image.Dispose();
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < 1000; i++)
{
ImageHolder h = new ImageHolder() { Image = new Bitmap(1000, 1000) };
}
}
}
内存使用率上升和上升,直到我得到异常(有时是ArgumentException,有时是OutOfMemory Exception)。
我的问题不是我能做些什么(我可以在ImageHolder中实现IDisposable,例如使用using-block)。
我的问题是:为什么垃圾收集不会破坏ImageHolder类型的对象(从不调用析构函数),因为没有对它们的引用而且我的内存不足!
感谢您的解释,
菲利普
答案 0 :(得分:7)
Bitmap类是一大堆非托管代码的托管类包装器,名为GDI +。包装器本身使用非常少的内存,实际的位图像素(通过非托管代码)存储在非托管内存中。垃圾收集器无法触及该内存,它只能看到包装器。这也是Bitmap具有Dispose()方法的原因,它释放了非托管内存。
你得到的OOM是GDI +告诉包装器它不能再分配非托管内存了。是,或者当GDI +随机决定你传递的宽度或高度太大而不是抛出OOM时ArgumentException。 GDI +因抛出无信息的例外而臭名昭着。
不会调用终结器,因为您的程序首先会对GDI +异常进行轰炸。失败的内存分配是不来自垃圾收集堆的内存分配,它是不能再分配的非托管代码。
终结器代码错误,当终结器运行时,位图可能已经自行完成。你必须让ImageHolder实现IDisposable,如下所示:
class ImageHolder : IDisposable {
public Image Image;
public void Dispose() {
if (Image != null) {
Image.Dispose();
Image = null;
}
}
}
现在你已经准备好防止OOM:
for (int i = 0; i < 1000; i++) {
using (var h = new ImageHolder() { Image = new Bitmap(1000, 1000) }) {
// do something with h
//...
}
}
如果你真的做需要存储数千个大图像,那么你需要一台可以提供1000 x 1000 x 1000 x 4 = 4千兆字节虚拟内存的机器。这是可能的,64位操作系统可以为您提供。
让您远离麻烦的一般经验法则是,实现您自己的析构函数非常。 .NET类的工作是为非托管资源提供包装器。像Bitmap一样。那些包装类有一个终结器,你不需要(也不应该)自己提供。 99.99%的情况是您需要实现IDisposable,因此您可以在.NET类实例上调用Dispose()。即使您想要管理自己的操作系统资源,那么仍然不会。您应该使用其中一个SafeHandle包装器。