我在处理大量图片(按顺序,而不是并行)时偶然发现了 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 类型的对象(永远不会调用析构函数),因为它们没有引用而且我的内存不足!
感谢您的解释,
菲利普
最佳答案
Bitmap 类是一个托管类包装器,它围绕着一大块称为 GDI+ 的非托管代码。包装器本身使用很少的内存,实际的位图像素存储(由非托管代码)在非托管内存中。垃圾收集器无法触及该内存,它只能看到包装器。这也是 Bitmap 具有 Dispose() 方法的原因,它释放非托管内存。
您得到的 OOM 是 GDI+ 告诉包装器它不能再分配非托管内存。是,或者当 GDI+ 随机决定您传递的 Width 或 Height 太大而不是抛出 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 GB 虚拟内存的机器。这是可能的,64 位操作系统可以满足您的需求。
让您远离此类麻烦的一般经验法则是极少实现您自己的析构函数。 .NET 类的工作是提供非托管资源的包装器。像位图。这些包装类有一个终结器,你不需要(也不应该)提供你自己的。 99.99% 的情况是您需要实现 IDisposable,以便您可以在 .NET 类实例上调用 Dispose()。即使您很想管理自己的操作系统资源,您仍然没有这样做。您应该使用其中一种 SafeHandle 包装器。
关于.net - 为什么我得到 OutOfMemory Exceptions 尽管可以释放内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4004654/