代码之家  ›  专栏  ›  技术社区  ›  Philipp

虽然内存可以释放,但为什么我要摆脱内存异常?

  •  3
  • Philipp  · 技术社区  · 14 年前

    我在处理大量图片时偶然发现了OutOfMemory异常(顺序,而不是并行)。我在一小部分代码中复制了这种行为,如下所示:

    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) };
            }
        }
    }
    

    我的问题不是我能做什么(例如,我可以在ImageHolder中实现IDisposable并使用using块)。

    谢谢你的解释,

    菲利普

    1 回复  |  直到 14 年前
        1
  •  7
  •   Hans Passant    14 年前

    Bitmap类是一个托管类包装器,用于包装一大块名为GDI+的非托管代码。包装器本身使用的内存很少,实际的位图像素(由非托管代码)存储在非托管内存中。垃圾回收器无法触及该内存,只能看到包装器。这也是位图有Dispose()方法的原因,它释放了非托管内存。

    你得到的OOM是GDI+告诉包装器它不能再分配非托管内存了。是的,或者当GDI+随机决定您通过的宽度或高度太大而不是抛出OOM时发生ArgumentException。GDI+因抛出非信息性异常而臭名昭著。

    其中一个来自垃圾收集堆,是无法再分配的非托管代码。

    终结器代码错误,当终结器运行时,位图可能已经自己完成。您必须让ImageHolder实现IDisposable,如下所示:

        class ImageHolder : IDisposable {
            public Image Image;
    
            public void Dispose() {
                if (Image != null) {
                    Image.Dispose();
                    Image = null;
                }
            }
        }
    

            for (int i = 0; i < 1000; i++) {
                using (var h = new ImageHolder() { Image = new Bitmap(1000, 1000) }) { 
                    // do something with h
                    //...
                }
            }
    

    如果你真的 需要存储1000个这样的大图像,然后需要一台能够提供1000 x 1000 x 1000 x 4=4 GB虚拟内存的计算机。这是可能的,64位操作系统可以给你。

    一般的经验法则是这样的 极其 很少实现自己的析构函数。NET类的工作是为非托管资源提供包装器。像位图。这些包装类有一个终结器,您不需要(也不应该)提供自己的终结器。99.99%的情况是,您需要实现IDisposable,以便可以对.NET类实例调用Dispose()。即使你想管理自己的操作系统资源,那么 仍然 不要。你应该用一个安全手柄包装纸。