.net - 为什么我得到 OutOfMemory Exceptions 尽管可以释放内存?

标签 .net garbage-collection

我在处理大量图片(按顺序,而不是并行)时偶然发现了 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/

相关文章:

c# - 识别对象列表中属性的最小值和最大值

Python垃圾回收发生在变量重置中

flash - AS3、垃圾收集和带监听器的多级 Sprite

java - GC能走多远

c# - Linq-To-Sql 实体的接口(interface)

.net - 错误模板显示在其他控件之上,而本应隐藏它

.net - 如何从 Metro P/Invoke 到 native dll?

c# - 我需要一个 LINQ 表达式来查找元素名称和属性与输入节点匹配的 XElement

garbage-collection - 在 D 中制作结构的堆副本

python - python 真实内存与分析内存