为什么我的程序的内存使用率会快速上升然后趋于平稳?

时间:2019-07-03 04:40:58

标签: c# memory-leaks garbage-collection

大家好,我正在制作一个C#WinForms应用程序,该应用程序在目录中搜索重复的图像。首先从目录中的每个图像调用构造函数。

目录中有很多文件,内存迅速上升到2gb,然后程序将抛出内存不足异常。

现在,我在for循环中添加了一个检查,以检查内存是否已超过800兆位,并强制执行垃圾回收。但是我注意到在第一次强制收集之后,内存不再增加。 (强制垃圾回收发生在〜800中的〜180循环中,然后再也不会发生)

VIsual studio diagnostic tools memory usage chart over time. Shows a sharp rise then a flat. (它看起来有点像鱼翅在水中游动,波涛汹涌。)

我为为什么会发生这种情况而感到困惑,因此来到这里寻求帮助。

private void GeneratePrints()
{
    for (int i = 0; i < files.Count; i++)
    {
        if (imageFileExtensions.Contains(Path.GetExtension(files[i])))
            prints.Add(new FilePrint(directory + "/" + files[i]));

        //800 Megabits
        const long MAX_GARBAGE = 800 * 125000;

        if (GC.GetTotalMemory(false) > MAX_GARBAGE)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }

    Console.WriteLine("File Prints Complete.");
}

一旦选择目录,GeneratePrints()将被调用1次。

我还将向您展示FilePrint类的构造函数。 我很确定这一切与MemoryStream对象有关。

public FilePrint(string filename)
{
    Bitmap img;

    using (var fs = new FileStream(filename, FileMode.Open))
    {
        using (var ms = new MemoryStream())
        {
            fs.CopyTo(ms);
            ms.Position = 0;
            img = (Bitmap)Bitmap.FromStream(ms);

            ms.Close();
        }

        fs.Close();
    }

    this.size = img.Size;
    img = ResizeImage(img, 8, 8);
    img = MakeGrayscale(img);

    //I do some basic for-loop arithmetic here 
    //calculating the average color of the image, not worth posting.

    img.Dispose();
}

所以基本上我想知道如何做到这一点,以免一开始就不会出现“类似鲨鱼鳍”的内存使用高峰,因此我不必强制进行垃圾收集。

这是发生强制垃圾回收时拍摄的内存快照(我没有正确处理MemoryStreams吗?):

Visual studio memory usage snapshot shows large MemorySteam size.

感谢您的答复,想法和答案!

1 个答案:

答案 0 :(得分:1)

您没有显示方法ResizeImage(img, 8, 8)MakeGrayscale(img),但是很可能它们只是基于旧方法创建并返回新图像。如果是这样,您的代码会构造两个Bitmap对象,但从未明确对其进行处置,因此请尝试处置它们,例如如下:

using (var old = img)
    img = ResizeImage(old, 8, 8);
using (var old = img)
    img = MakeGrayscale(old);

您可能还想保证使用img处理最后的try/finally

Bitmap img = null;
try
{
    img = new Bitmap(filename);  // Here I simplified the code, but this will leave the file locked until `img` is disposed after resizing.

    this.size = img.Size;
    using (var old = img)
        img = ResizeImage(old, 8, 8);
    using (var old = img)
        img = MakeGrayscale(old);

    //I do some basic for-loop arithmetic here 
    //calculating the average color of the image, not worth posting.
}
finally
{
    if (img != null)
        img.Dispose();
}

您可能会长时间使用大量内存,然后急剧下降,这可能是最终最终确定了未处理映像的非托管资源,但是,因为GC并未意识到由所有者拥有的非托管内存未处置的Bitmap对象,不一定插入,将位图标识为未引用,并在相当长的一段时间内将其传递给finalizer thread。很难预测终结器线程何时启动甚至开始工作。请参见 Are .net finalizers always executed? ,答案不一定是 。但是,致电GC.WaitForPendingFinalizers();可能会启动该过程。

顺便说一句,MemoryStream实际上不需要 放置在当前实现中,因为它缺少不受管理的资源。参见this answerJon Skeet Is a memory leak created if a MemoryStream in .NET is not closed? 进行确认。 (这样做仍然是一个好习惯,尽管在Bitmap的情况下,讨厌的文件/流锁定使它不可能。)