关于位图,图像和放大器的几点疑问`使用'块

时间:2010-04-01 13:06:12

标签: c# .net

我赶上了这个问题。

Garbage Collector not doing its job. Memory Consumption = 1.5GB & OutOFMemory Exception

我觉得我的理解有些不对劲。请澄清这些事情。

  1. Destructor& IDisposable.Dispose是释放不受.NET控制的资源的两种方法。这意味着除了记忆以外的一切。正确?
  2. using块只是调用对象的IDisposable.Dispose()方法的更好方法。
  3. 这是我所指的主要代码。

    class someclass
    {
        static someMethod(Bitmap img)
        {
            Bitmap bmp = new Bitmap(img); //statement1
            // some code here and return
        }
    }
    

    这是我用于测试的课程:

    class someotherClass
    {
        public static voide Main()
        {
            foreach (string imagePath in imagePathsArray)
            {
                using (Bitmap img1 = new Bitmap(imagePath))
                {
                    someclass.someMethod(img1);
                    // does some more processing on `img1`
                }
            }
        }
    }
    

    statement1是否有内存泄漏?

    问题1:如果每个图像大小都是10MB。然后这个bmp对象占用至少10MB?我的意思是,它会制作整个图像的全新副本吗?或者只是参考它?

    问题2:我应该或不应该将声明1放在using块中吗?

    我的论点:我们不应该。因为using不是用于释放内存而是用于释放资源(在这种情况下是文件句柄)。 如果我在using块中使用它。它会关闭此bmp对象封装的文件句柄。这意味着我们也在关闭文件句柄 对于调用者的img1对象。哪个不正确?

    内存泄漏。这里没有内存泄漏的范围。因为返回此方法时会销毁引用bmp。 这留下了没有任何指针的内存。所以,它收集了垃圾。我是对的吗?

    修改

    class someclass
    {
        static Bitmap someMethod(Bitmap img)
        {
           Bitmap bmp = new Bitmap(img); //can I use `using` block on this enclosing `return bmp`; ???
            // do some processing on bmp here
           return bmp;
        }
    }
    

3 个答案:

答案 0 :(得分:1)

您将内存等同于托管资源,这根本不一定是相同的。

如果您创建一个实现IDisposable的类型的新对象,则应在完成后对其进行处理。周期。

事情的真相是,除非用反射器之类的东西反汇编Bitmap类,否则你将无法知道它如何处理在其构造函数中传递的其他实例,或者它的Dispose方法是什么实际上。包含IDisposable接口应该被视为来自类的开发人员的指令,以便在完成后调用Dispose方法。您之前的问题表明您正在观察内存泄漏;我们已经指出您没有处理所有内容 - 您是否尝试添加我们建议的dispose调用并查看是否已解决内存泄漏问题?

答案 1 :(得分:1)

内存泄漏在.NET中非常罕见。你所拥有的是资源泄漏(GDI)。从技术上讲,这些资源是“内存”,但它们来自一个特殊的保留内存池,它比托管对象堆(存储大多数对象数据)要小得多,并且.NET垃圾收集器不管理这个特定的存储器中。

不将Bitmap放置在创建它的同一例程中自动导致资源泄漏。事实上,由于该类有一个终结器,它可能在合理的时间内清理干净;问题是,你不知道什么时候,如果你创建了太多Bitmap个对象而没有清理它们(使用Dispose),你冒着使用GDI资源的操作系统的风险,你最终得到了系统范围内的奇怪行为,就像无法恢复最小化的窗口或文本无法在屏幕上正确显示。

当您使用IDisposable个对象时,您需要确定的是,当您完成它们时,您最终会将它们最终。以下代码没有任何问题:

Bitmap CreateBitmap(...)
{
    Bitmap bmp = new Bitmap(400, 400);
    // Do something with the bitmap
    bmp.SetPixel(1, Color.Red);
    // etc.
    return bmp;
}

此方法的方法名称和返回类型都清楚地表明它是分配而不是释放资源。它是创建一个保留非托管资源的对象,由调用者来清理。只要您以这种方式使用方法:

using (Bitmap bmp = CreateBitmap(...))
{
    // Do something else with the bitmap
}

...那你就没事了。

语义错误是创建Bitmap并简单地让引用超出范围:

public int PossiblyLeakyMethod()
{
    Bitmap bmp = CreateBitmap(...);
    return bmp.Height;
}

这很糟糕。现在再也没有引用Bitmap了。垃圾收集器可能决定很快完成它,因为它没有足够长的时间从Gen0升级。但是它仍然具有语义,这意味着当它超出范围时(即当方法完成执行时)它会被完成。

正如我在其他答案中提到的,当您使用IDisposable时,您也不应该假设有关对象的私有实现。大多数核心框架IDisposable类都有终结器,但是其他人的库可能在没有终结器的情况下实现了破坏,在这种情况下,上面的“可能”泄漏方法将保证泄漏。 / p>

即使存在终结器,也完全有可能以比GC可以处理的速度更快的速度创建对象:

public void Crash()
{
    while (true)
    {
        Bitmap bmp = new Bitmap(1920, 1200);
    }
}

让这个运行,看看获得OutOfMemoryException需要多长时间。 Bitmap在这里有终结者并不重要;问题是你正在耗尽的“内存”与GC管理的内存不同,终结器运行速度不够快,无法释放所有非托管资源。只有当你的托管内存耗尽时,GC才会启动。

此处的最后一点:处置然后返回一个对象也是一个错误。以下代码将崩溃:

public Bitmap CreateUselessBitmap()
{
    using (Bitmap bmp = new Bitmap(400, 400))
    {
        // Do something with bitmap
        return bmp;
    }
}

在这里你要归还已被处理的东西。如果调用者尝试对您返回的Bitmap执行任何重要操作,则会失败。

因此,您需要确保Dispose IDisposableBitmap个对象,但只有才能实际使用它们。如果某个方法的目的是创建一个稍后用于其他内容的using,那么不要将其放在Dispose语句中该方法,但执行命名并记录方法以清楚地表明它分配资源,以便编写代码来调用该方法的程序员知道他需要{{1}}结果。

希望清除一些事情。

答案 2 :(得分:0)

  

析构者& IDisposable.Dispose是两种释放非资源的方法   在.NET的控制下。这意味着除了记忆以外的一切。正确?

是的,你是对的。

  

使用块只是调用对象的IDisposable.Dispose()方法的更好方法。

他们不是一个“更好”的方式。有时候更方便。

  

statement1是否有任何内存泄漏?

我不这么认为,虽然我不确定。如果位图包含终结器,那么最终会调用它。

P.S。 我认为this文章可以帮助您。