c# - 我如何让 .NET 积极地进行垃圾收集?

标签 c# .net memory-management garbage-collection

我有一个用于图像处理的应用程序,我发现自己通常分配 4000x4000 ushort 大小的数组,偶尔也会分配 float 等。目前,.NET 框架在这个应用程序中似乎是随机崩溃的,几乎总是出现内存不足错误。 32mb 并不是一个巨大的声明,但如果 .NET 正在产生内存碎片,那么如此大的连续分配很可能不会按预期运行。

有没有办法告诉垃圾收集器更积极,或者对内存进行碎片整理(如果这是问题所在)?我意识到存在 GC.Collect 和 GC.WaitForPendingFinalizers 调用,并且我已经在我的代码中大量使用它们,但我仍然遇到错误。可能是因为我正在调用大量使用 native 代码的 dll 例程,但我不确定。我已经检查了那个 C++ 代码,并确保我声明的任何内存都被删除了,但我仍然遇到这些 C# 崩溃,所以我很确定它不在那里。我想知道 C++ 调用是否会干扰 GC,使其留下内存,因为它曾经与 native 调用交互——这可能吗?如果可以,我可以关闭该功能吗?

编辑:这是一些会导致崩溃的非常具体的代码。根据this SO question ,我不需要在这里处理 BitmapSource 对象。这是原始版本,其中没有 GC.Collects。它通常在撤消过程的第 4 到 10 次迭代时崩溃。由于我使用的是 WPF,此代码替换了空白 WPF 项目中的构造函数。由于我在下面对@dthorpe 的回答中解释的限制以及 this SO question 中列出的要求,我对位图源进行了古怪的处理。 .

public partial class Window1 : Window {
    public Window1() {
        InitializeComponent();
        //Attempts to create an OOM crash
        //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
        int theRows = 4000, currRows;
        int theColumns = 4000, currCols;
        int theMaxChange = 30;
        int i;
        List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
        byte[] displayBuffer = null;//the buffer used as a bitmap source
        BitmapSource theSource = null;
        for (i = 0; i < theMaxChange; i++) {
            currRows = theRows - i;
            currCols = theColumns - i;
            theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
            displayBuffer = new byte[theList[i].Length];
            theSource = BitmapSource.Create(currCols, currRows,
                    96, 96, PixelFormats.Gray8, null, displayBuffer,
                    (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            System.Console.WriteLine("Got to change " + i.ToString());
            System.Threading.Thread.Sleep(100);
        }
        //should get here.  If not, then theMaxChange is too large.
        //Now, go back up the undo stack.
        for (i = theMaxChange - 1; i >= 0; i--) {
            displayBuffer = new byte[theList[i].Length];
            theSource = BitmapSource.Create((theColumns - i), (theRows - i),
                    96, 96, PixelFormats.Gray8, null, displayBuffer,
                    ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            System.Console.WriteLine("Got to undo change " + i.ToString());
            System.Threading.Thread.Sleep(100);
        }
    }
}

现在,如果我明确调用垃圾收集器,我必须将整个代码包装在一个外部循环中以导致 OOM 崩溃。对我来说,这往往发生在 x = 50 左右:

public partial class Window1 : Window {
    public Window1() {
        InitializeComponent();
        //Attempts to create an OOM crash
        //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
        for (int x = 0; x < 1000; x++){
            int theRows = 4000, currRows;
            int theColumns = 4000, currCols;
            int theMaxChange = 30;
            int i;
            List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack
            byte[] displayBuffer = null;//the buffer used as a bitmap source
            BitmapSource theSource = null;
            for (i = 0; i < theMaxChange; i++) {
                currRows = theRows - i;
                currCols = theColumns - i;
                theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
                displayBuffer = new byte[theList[i].Length];
                theSource = BitmapSource.Create(currCols, currRows,
                        96, 96, PixelFormats.Gray8, null, displayBuffer,
                        (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            }
            //should get here.  If not, then theMaxChange is too large.
            //Now, go back up the undo stack.
            for (i = theMaxChange - 1; i >= 0; i--) {
                displayBuffer = new byte[theList[i].Length];
                theSource = BitmapSource.Create((theColumns - i), (theRows - i),
                        96, 96, PixelFormats.Gray8, null, displayBuffer,
                        ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
                GC.WaitForPendingFinalizers();//force gc to collect, because we're in scenario 2, lots of large random changes
                GC.Collect();
            }
            System.Console.WriteLine("Got to changelist " + x.ToString());
            System.Threading.Thread.Sleep(100);
        }
    }
}

如果我在任何一种情况下对内存处理不当,如果有什么我应该用分析器发现的,请告诉我。这是一个非常简单的例程。

不幸的是,@Kevin 的回答似乎是正确的——这是 .NET 中的一个错误以及 .NET 如何处理大于 85k 的对象。这种情况让我觉得非常奇怪。 Powerpoint 是否可以在具有这种限制的 .NET 或任何其他 Office 套件应用程序中重写? 85k 在我看来并不是一个很大的空间,而且我还认为任何经常使用所谓“大”分配的程序在使用 .NET 时会在几天到几周内变得不稳定。

编辑:看起来 Kevin 是对的,这是 .NET GC 的限制。对于那些不想关注整个线程的人,.NET 有四个 GC 堆:gen0、gen1、gen2 和 LOH(大型对象堆)。 85k 或更小的所有内容都放在前三个堆之一中,具体取决于创建时间(从 gen0 移动到 gen1 再到 gen2,等等)。大于 85k 的对象被放置在 LOH 上。 LOH 从不 压缩,因此最终,我正在执行的类型分配最终会导致 OOM 错误,因为对象分散在该内存空间中。我们发现迁移到 .NET 4.0 确实在一定程度上帮助解决了这个问题,延迟了异常,但没有阻止它。老实说,这感觉有点像 640k 的障碍——85k 应该足以满足任何用户应用程序的要求(解释 .NET 中 GC 的讨论 this video)。郑重声明,Java 的 GC 不会表现出这种行为。

最佳答案

这里有一些文章详细介绍了大对象堆的问题。这听起来像是您可能遇到的情况。

http://connect.microsoft.com/VisualStudio/feedback/details/521147/large-object-heap-fragmentation-causes-outofmemoryexception

大对象堆的危险:
http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

这是一个关于如何在大对象堆 (LOH) 上收集数据的链接:
http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

据此,似乎没有办法压缩 LOH。我找不到任何更新的内容明确说明如何执行此操作,因此它似乎在 2.0 运行时中没有更改:
http://blogs.msdn.com/maoni/archive/2006/04/18/large-object-heap.aspx

处理这个问题的简单方法是尽可能制作小物体。您的另一个选择是只创建几个大对象并一遍又一遍地重复使用它们。这不是一个理想的情况,但它可能比重写对象结构更好。由于您确实说过创建的对象(数组)大小不同,这可能很困难,但它可以防止应用程序崩溃。

关于c# - 我如何让 .NET 积极地进行垃圾收集?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2860917/

相关文章:

ios - iOS 上使用 Memory Monitor 时虚拟内存消耗和实际内存的区别

c# - 字符串到 Lucene 查询

检查 stdin 中读取的行格式为 number^number

c++ - "bit padding"或 "padding bits"到底是什么?

c# - 有没有办法让编译器根据项目的目录结构确定我的类的命名空间?

c# - 为什么静态字段通常被认为是线程安全的?

c# - 从 Windows 窗体应用程序调用 Google Maps API V3 Javascript API?

c# - 填充 ComboBox DataColumn 项和值

c# - 使用 linq 在某个日期范围内每月排名前 5 的客户端性能

c# - 在 LINQ 查询中填充对象数组