我如何获得.NET垃圾积极收集?

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

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

编辑:这是一些非常具体的代码,将导致崩溃。 根据这个SO问题 ,我不需要在这里处理BitmapSource对象。 这里是天真的版本,没有GC.Collect在里面。 它通常在撤消过程的迭代4到10中崩溃。 这段代码replace了一个空白的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); } } } 

现在,如果我明确地调用垃圾收集器,我必须将整个代码包装在一个外部循环中,以引起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); } } } 

如果我在任何一种情况下处理内存不当,如果有一件事情我应该与一个探查器发现,让我知道。 这是一个非常简单的例程。

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

编辑 :它看起来像凯文是正确的,这是.NET的GC的限制。 对于那些不想跟随整个线程的人来说,.NET有四个GC堆:gen0,gen1,gen2和LOH(大对象堆)。 根据创build时间(从gen0移到gen1到gen2等),前三个堆中的任何一个都是85k或更小。 大于85k的物体被放置在LOH上。 LOH 永远不会被压缩,所以最终,我所做的types的分配最终会导致OOM错误,因为对象在内存空间中分散。 我们发现,迁移到.NET 4.0在某种程度上帮助解决了这个问题,延迟了exception,但是并没有阻止它。 说实话,这感觉有点像640k的障碍 – 对于任何用户应用程序来说,85k应该足够了(用.NET解释这个讨论GC的video )。 为了logging,Java不会在GC中performance出这种行为。

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

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

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

处理这个问题的简单方法是尽可能地制作小物件。 你的另一个select是只创build一些大的对象,并重复使用它们。 不是一个想法的情况,但它可能比重写对象结构更好。 既然你说过创build的对象(数组)是不同的大小,可能会有困难,但它可以防止应用程序崩溃。

首先缩小问题所在。 如果你有一个本地内存泄漏,戳GC不会为你做任何事情。

运行perfmon并查看.NET堆大小和专用字节计数器。 如果堆大小保持相当稳定,但是私有字节增长,那么你就有一个本地代码问题,你需要分解C ++工具来debugging它。

假设问题与.NET堆,你应该运行一个像Redgate的ant分析器或JetBrain的DotTrace的代码分析器。 这将告诉你哪些物体占用了空间而不是很快被收集。 你也可以使用WinDbg和SOS来实现这个function,但是这个界面很好(虽然function强大)。

一旦你find了违规项目,应该更明白如何处理它们。 一些导致问题的东西是静态字段引用对象,事件处理程序不被注销,对象生活足够长的时间进入第二代,但随后死亡等等等等没有内存堆的configuration文件,你将不会能够找出答案。

不pipe你做什么,“自由地洒”GC.Collect调用几乎总是尝试和解决问题的错误方法。

有一个外部的机会,切换到GC的服务器版本将改善的东西(只是在configuration文件中的一个属性) – 默认的工作站版本是为了保持一个用户界面的响应,所以将有效地放弃大,长期运行的select。

使用Process Explorer(来自Sysinternals)查看您的应用程序的大对象堆是什么。 你最好的select是使你的arrays更小,但有更多的。 如果你可以避免在LOH上分配你的对象,那么你将不会得到OutOfMemoryExceptions,你也不需要手动调用GC.Collect。

LOH不会被压缩,只会在结尾处分配新的对象,这意味着您可能会很快耗尽空间。

如果在非托pipe库(即GC不知道的内存)中分配大量内存,则可以使用GC.AddMemoryPressure方法使 GC知道它。

当然,这在一定程度上取决于非托pipe代码在做什么。 你没有具体说明它是分配内存,但我得到的印象是这样的。 如果是这样,那么这正是该方法所devise的。 然后,如果非托pipe库分配了大量内存,那么也有可能将内存碎片化,即使使用AddMemoryPressure也完全超出了GC的控制AddMemoryPressure 。 希望情况并非如此; 如果是这样,你可能不得不重构图书馆或改变它的使用方式。

PS当你最终释放非托pipe内存时,不要忘记调用GC.RemoveMemoryPressure 。

(PPS其他一些答案可能是正确的,这很可能是你的代码中的内存泄漏,特别是如果它是image processing,我敢打赌,你没有正确处理你的IDIsposable实例。以防万一这些答案不能引导你到任何地方,这是你可以采取的另一条路线。)

另外:当一个函数返回给调用者时,.NET垃圾回收器执行一个“快速”GC。 这将处理函数中声明的局部variables。

如果你构造你的代码,使得你有一个在循环中反复分配大块的大函数,把每个新块分配给同一个局部variables,那么GC可能不会在一段时间内回滚未被引用的块。

另一方面,如果你构造你的代码,使得你有一个带有一个调用内部函数的循环的外部函数,并且内存被分配并分配给该内部函数中的一个局部variables,那么GC应该在内部函数返回给调用者并回收刚刚分配的大内存块,因为它是返回的函数中的局部variables。

避免GC.Collect的诱惑。

你testing过内存泄漏吗? 我一直在使用.NET Memory Profiler ,在一个项目中有相当多的成功,这个项目有一些非常微妙和令人讨厌的持久(双关意图)内存泄漏。

就像完整性检查一样,确保您在实现IDisposable任何对象上调用Dispose

你可以实现你自己的数组类,把内存分成非连续的块。 比方说,有一个64×64arrays的[64,64] ushortarrays分配和解除分配。 然后只是映射到正确的一个。 位置66,66将位于[1,1]位置[2,2]。

那么,你应该可以躲避大对象堆。

除了以更友善的方式处理分配(例如重新使用数组等),现在还有一个新的select:您可以手动导致LOH的压缩。

 GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; 

这将导致下一次发生gen-2收集时发生LOH压缩(无论是GC.Collect收集还是通过显式调用GC.Collect )。

请注意,不要压缩LOH通常是一个好主意 – 只是您的场景足以满足手动压实的需要。 LOH通常用于巨大的,长期存在的对象 – 比如预先分配的缓冲区,可以随着时间的推移重用。

如果你的.NET版本不支持这一点,你也可以尝试分配两个幂的大小,而不是精确地分配你需要的内存量。 这是很多本地分配器所做的,以确保内存碎片不会变得不可能的愚蠢(它基本上把最大的堆碎片的上限)。 这很烦人,但如果你可以限制处理这个代码的一小部分的代码,这是一个体面的解决方法。

请注意,您仍然必须确保实际上可以压缩堆 – 任何固定的内存将防止堆中的压缩。

另一个有用的select是使用分页 – 从不分配超过64kB的连续空间。 这意味着你将完全避免使用LOH。 在你的情况下,在一个简单的“数组包装”中pipe理这个并不难。 关键是在性能要求和合理的抽象之间保持一个很好的平衡。

当然,作为最后的手段,你总是可以使用不安全的代码。 这为处理内存分配提供了很大的灵活性(尽pipe比使用C ++更加痛苦) – 包括允许您显式分配非托pipe内存,完成您的工作并手动释放内存。 再说一次,如果你可以把这个代码隔离到你的总代码库的一小部分,那么这是唯一有意义的事情,并且确保你有一个安全的内存pipe理包装器,包括适当的终结器(保持一定程度的内存安全) 。 在C#中这不是太难,但是如果你发现自己经常这样做,那么使用C ++ / CLI作为代码的这些部分可能是个好主意,并且可以从C#代码中调用它们。

这个问题很可能是由于你在内存中有大量的对象。 如果它们的大小是可变的,那么碎片将成为一个更可能的问题(虽然它仍然是一个问题)。您在评论中指出,您正在为映像文件在内存中存储撤消堆栈。 如果将其移动到“磁盘”,则可以节省大量的应用程序内存空间。

将撤消操作移动到磁盘上也不会对性能造成太大的负面影响,因为这不是你一直使用的东西。 (如果它确实成了一个瓶颈,你总是可以创build一个混合磁盘/内存caching系统。)

扩展…

如果您确实担心将撤销数据存储在文件系统上可能会对性能造成影响,则可以考虑虚拟内存系统很有可能将此数据分页到您的虚拟页面文件。 如果您为这些撤消文件创build了自己的页面文件/交换空间,您将拥有能够控制何时何地调用磁盘I / O的优势。 不要忘记,即使我们都希望我们的电脑拥有无限的资源,但它们是非常有限的。

1.5GB(可用应用程序内存空间)/ 32MB(大内存请求大小)〜46

你可以使用这个方法:

 public static void FlushMemory() { Process prs = Process.GetCurrentProcess(); prs.MinWorkingSet = (IntPtr)(300000); } 

三种使用这种方法的方法。

1 – configuration了类之类的pipe理对象之后,…

2 – 创build2000个时间间隔的计时器。

3 – 创build线程来调用这个方法。

我build议你在线程或定时器中使用这个方法。

最好的办法就像这篇文章所展示的那样,它是用西class牙文,但是你确实懂得了代码。 http://www.nerdcoder.com/c-net-forzar-liberacion-de-memoria-de-nuestras-aplicaciones/

这里的情况下链接代码得到brock

 using System.Runtime.InteropServices; .... public class anyname { .... [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)] private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize); public static void alzheimer() { GC.Collect(); GC.WaitForPendingFinalizers(); SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); } 

….

你叫alzheimer()清理/释放内存。

GC不考虑非托pipe堆。 如果你正在创build大量的仅仅是C#包装器的对象到更大的非托pipe内存,那么你的内存就被吞噬了,但是GC不能根据这个做出理性的决定,因为它只能看到托pipe堆。

你最终在GC认为你没有记忆力的情况下,因为第一代堆中的大部分东西都是8字节的引用,实际上它们就像海上的冰山。 大部分内存在下面!

您可以使用这些GC调用:

  • 系统:: GC :: AddMemoryPressure(sizeOfField);
  • 系统:: GC :: RemoveMemoryPressure(sizeOfField);

这些方法允许GC查看非托pipe内存(如果您提供了正确的数字)。