我有一个用于图像处理的应用程序,我发现自己通常分配 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://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/