如何从多个托管应用程序的大对象堆LOH中取回未使用的内存?

在与同事谈论启动时使用近1.5G内存的特定应用程序组时…他指出了一个关于.NET生产调试的非常好的链接

困惑的部分是……

例如,如果为单个块分配1 MB内存,则大对象堆的大小​​将扩展为1 MB。 释放此对象时,大对象堆不会解除对虚拟内存的影响,因此堆的大小保持为1 MB。 如果稍后再分配另一个500 KB块,则新块将在属于大对象堆的1 MB内存块中分配。 在进程生命周期中,大对象堆总是增长以保存当前引用的所有大块分配,但是在释放对象时从不收缩,即使发生了垃圾收集。 下一页的图2.4显示了一个大对象堆的示例。

现在让我们假设我们有一个虚构的应用程序,可以创建一大堆大对象(> 85KB),所以大对象堆增长让我们说200 Meg。 现在假设我们有10个这样的app实例正在运行..所以分配了2000 Megs。 现在这个记忆永远不会回到操作系统,直到过程关闭……(就是我所理解的)

我的理解是否有任何差距? 我们如何在各种LOHeaps中找回未使用的内存; 我们不创造OutOfMemoryExceptions的完美风暴?

更新:从Marc的回复中,我想澄清LOH对象没有被引用 – 大对象是use-n-throw – 但是即使堆在初始激增之后堆相对空,堆也不会收缩。

更新#2:只是包含一个代码片段(夸大但我觉得有点了)..我看到一个OutOfMemoryException,虚拟内存在我的机器上达到1.5G标记(1.7G在另一个上)…来自Eric L 。博客文章 ,’进程内存可以被视为磁盘上的大量文件……’ – 因此这个结果是出乎意料的。 在这种情况下,机器在HDD上具有GB的可用空间。 PageFile.sys OS文件(或相关设置)是否施加了任何限制?

static float _megaBytes; static readonly int BYTES_IN_MB = 1024*1024; static void BigBite() { try { var list = new List(); int i = 1; for (int x = 0; x < 1500; x++) { var memory = new byte[BYTES_IN_MB + i]; _megaBytes += memory.Length / BYTES_IN_MB; list.Add(memory); Console.WriteLine("Allocation #{0} : {1}MB now", i++, _megaBytes); } } catch (Exception e) { Console.WriteLine("Boom! {0}", e); // I put a breakpoint here to check the console throw; } } static void Main(string[] args) { BigBite(); Console.WriteLine("Check VM now!"); Console.ReadLine(); _megaBytes = 0; ThreadPool.QueueUserWorkItem(delegate { BigBite(); }); ThreadPool.QueueUserWorkItem(delegate { BigBite(); }); Console.ReadLine(); // will blow before it reaches here } 

我想首先澄清一下。 – 假设您正在运行应用程序作为32位应用程序,您的进程可用的VA空间仅为2 GB,如果您启用了大型地址空间交换机,则为3 GB,因此即使您有巨大的页面文件,如果您是32位也没关系过程,如果你运行64位,你有很大的地址空间,这很重要。

  • 在LOH上分配大小> 85000字节的对象,注意它是85000字节而不是85K,它也是可以改变的实现细节。 现在,回到你的问题。 GC将取消在两种情况下未使用的LOH段1-当机器上的内存压力很高时(~95-98%)2-当它无法满足新的分配请求时,它将解除未使用的在LOH的页面

所以你会在其中一个案例中找回记忆。 你在达到2GB限制之前击中OOM的事实可能意味着你有VA碎片,当你没有连续的VA地址空间来满足新的分配时会发生VA碎片,例如你要求8KB的段,你不要你的VA连续2页(假设页面大小为4 K)

您可以在Windows的调试工具中使用!vamap调试器扩展来validation这一点。

希望这有助于谢谢

如果LOH想要保留内存,那就是LOH – 但是,不要忘记OutOfMemoryException是按进程进行的,因为硬盘确实是虚拟内存的限制因素。 Eric Lippert最近在博客上发表了这篇文章 。 当然,这并不妨碍它从所有分页中获得糟糕的性能……

好吧,如果你真的有这种分配模式,你可以将你的大对象移动到另一个appdomain – 当你决定释放所有大对象时,释放appdomain并释放该appdomain的堆。