如何让.NET积极收集垃圾?

mmr*_*mmr 40 .net c# garbage-collection memory-management

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

有没有办法告诉垃圾收集器更积极,或碎片整理内存(如果这是问题)?我意识到有GC.Collect和GC.WaitForPendingFinalizers调用,我通过我的代码非常自由地散布它们,但我仍然得到错误.这可能是因为我正在调用使用本机代码的dll例程,但我不确定.我已经查看了那个C++代码,并确保我声明删除的任何内存,但我仍然得到这些C#崩溃,所以我很确定它不存在.我想知道C++调用是否会干扰GC,使其留下内存,因为它曾经与本机调用交互 - 这可能吗?如果是这样,我可以关闭该功能吗?

编辑:这是一些非常具体的代码,将导致崩溃.根据这个问题,我不需要在这里处理BitmapSource对象.这是天真的版本,没有GC.Collects.它通常在撤销过程的迭代4到10上崩溃.这段代码替换了空白WPF项目中的构造函数,因为我使用的是WPF.由于我在下面对@dthorpe的回答中解释的限制以及此SO问题中列出的要求,我对bitmapsource做了一些古怪的事情.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我明确地调用垃圾收集器,我必须将整个代码包装在外部循环中以导致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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我在任何一种情况下都错误处理了内存,如果有什么东西我应该发现一个分析器,请告诉我.那是一个非常简单的例程.

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

编辑:看起来凯文是对的,这是.NET的GC的限制.对于那些不想遵循整个线程的人,.NET有四个GC堆:gen0,gen1,gen2和LOH(大对象堆).根据创建时间(从gen0移动到gen1到gen2等),所有85k或更小的东西都在前三个堆中的一个上.大于85k的物体放置在LOH上.LOH 永远不会被压缩,所以最终,我正在进行的类型的分配最终会导致OOM错误,因为对象会分散在该内存空间中.我们发现迁移到.NET 4.0确实有点帮助了这个问题,延迟了异常,但没有阻止它.老实说,在.NET中讨论GC).对于记录,Java不会在其GC中出现此行为.

kem*_*002 22

以下是一些详细介绍大对象堆问题的文章.这听起来像你可能遇到的.

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/大对象heap.aspx

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


Pao*_*olo 22

首先缩小问题所在.如果您有本机内存泄漏,那么戳GC不会为您做任何事情.

运行perfmon并查看.NET堆大小和专用字节计数器.如果堆大小保持相当稳定但私有字节增长,那么你就会遇到本机代码问题,你需要打破C++工具来调试它.

假设问题出在.NET堆上,你应该对像Redgate的Ant分析器或JetBrain的DotTrace这样的代码运行一个分析器.这将告诉您哪些对象占用了空间而没有快速收集.您也可以将WinDbg与SOS一起使用,但它是一个繁琐的界面(虽然功能强大).

一旦找到了违规物品,应该更明白如何处理它们.导致问题的一些事情是引用对象的静态字段,未注册的事件处理程序,生存时间足以进入Gen2但随后很快就会死亡的对象等等.如果没有内存堆的配置文件,您将不会能够找到答案.

无论你做什么,"自由地洒"GC.Collect调用几乎总是尝试解决问题的错误方法.

切换到GC的服务器版本有可能改善事物(只是配置文件中的属性) - 默认工作站版本旨在保持UI响应,因此将有效地放弃大量长时间运行的选择.

  • +1 - ""自由地洒"`GC.Collect()`调用几乎总是错误的尝试和解决问题的方法." (31认同)