为什么这种方法消耗了这么多内存?或者更确切地说,为什么GC没有释放内存?

时间:2017-07-08 21:03:39

标签: c# image winforms image-processing

我正在构建一些需要图像平移,缩放和亮度控制的软件。我设置了一个小项目,在网上找到很多例子之后建立一个简单的亮度控制,所以请忽略我在主窗体中做的所有事情

当我开始使用轨道栏控制器时,当值(-100到100)发生变化时会触发下面的事件,内存使用量会以秒为单位叠加到千兆字节。然后它会坐在那里,永远不会释放记忆。再次移动轨迹栏会占用更多内存

private void trackBarBrightness_EditValueChanged(object sender, EventArgs e)
    {
        float value = trackBarBrightness.Value * 0.01f;
        float[][] colorMatrixElements =
        {
            new float[] {
                1,
                0,
                0,
                0,
                0
            },
            new float[] {
                0,
                1,
                0,
                0,
                0
            },
            new float[] {
                0,
                0,
                1,
                0,
                0
            },
            new float[] {
                0,
                0,
                0,
                1,
                0
            },
            new float[] {
                value,
                value,
                value,
                0,
                1
            }
        };

        ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
        ImageAttributes imageAttributes = new ImageAttributes();

        imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        //Image img = this.pictureEdit.Image;
        Image img = originalImage;
        Graphics g = default(Graphics);
        Bitmap bm = new Bitmap(Convert.ToInt32(img.Width), Convert.ToInt32(img.Height));
        g = Graphics.FromImage(bm);
        g.DrawImage(img, new Rectangle(0, 0, bm.Width, bm.Height), 0, 0, bm.Width, bm.Height, GraphicsUnit.Pixel, imageAttributes);
        pictureEdit.Image = bm;
    }

非常感谢任何指导

3 个答案:

答案 0 :(得分:4)

问题是您没有在Dispose上致电Bitmap。 .Net中的Bitmap本质上只是一个包含大型无人GDI +位图(source)的小型托管对象。正是这个GDI +位图存储像素数据,因此负责内存消耗。托管的.Net Bitmap只是一个包装器,相比之下,远远小于

GC.Collect()释放内存的原因是因为您强制GC释放所有不再被引用的位图,这些位图反过来释放它们指向的所有非托管内存。 GC不会自动为您执行此操作的原因是因为GC仅跟踪托管内存而非跟踪非托管内存。因此,从GC的角度来看,你留下的所有这些托管位图都很小,并没有占用大量空间,因此收集它们并不重要。事实上,当它们相当大时。 来自MSDN

  

如果小型托管对象分配大量非托管内存,则运行时仅考虑托管内存,因此低估了调度垃圾回收的紧迫性。

当您不再需要Dispose时调用Bitmap,您将立即释放GDI +位图使用的非托管内存。托管的.Net Bitmap仍然会一直闲逛,直到GC收集它,但是谁在乎,这很小。

答案 1 :(得分:1)

Bitmap继承自Image和Image有一个终结器!这使得它在创建时自动生成Gen1,如果GC没有感觉到内存压力,可能需要几分钟才能处理和释放。

更新的快节奏实际上是创建了许多实例,并且必须由终结器线程逐个收集。有时需要15分钟,有时甚至更早。

解决方案是随时限制位图的数量,并且在任何时刻只需要一个,最新的一个,当被替换时,前一个可以被处置,当前呈现直到下次更新。

然后你应该做这样的事情:

private void trackBarBrightness_EditValueChanged(object sender, EventArgs e)
{
    //your current code here up to (excluding): pictureEdit.Image = bm;
    IDisposable PreviousImage = pictureEdit.Image;
    pictureEdit.Image = bm;
    PreviousImage?.Dispose();
}

或者,如果PictureBox是只写的:

private IDisposable PreviousImage {get;set;}

private void trackBarBrightness_EditValueChanged(object sender, EventArgs e)
{
    //your current code here up to (excluding): pictureEdit.Image = bm;
    pictureEdit.Image = bm;
    PreviousImage?.Dispose();
    PreviousImage = bm;
}

答案 2 :(得分:0)

不要看到你正在使用任何循环,所以不确定,但你可以考虑使用内存分析器,但是我看到你处理创建的Image对象全部。我的意思是以下两行。您应该在其上明确调用Dispose()或使用Using(...) { ... }构造以便更好地处理

Image img = originalImage;
Graphics g = default(Graphics);