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

10

在与同事讨论一组应用程序在启动时使用了近1.5G的内存时...他给我指了一个非常好的链接.NET生产调试

例如,如果您向单个块分配1 MB内存,则大对象堆将扩展为1 MB大小。当您释放此对象时,大对象堆不会取消提交虚拟内存,因此堆的大小保持为1 MB。如果稍后分配另一个500-KB块,则新块将分配在属于大对象堆的1 MB内存块中。在进程的生命周期内,大对象堆始终增长以容纳当前引用的所有大块分配,但即使进行垃圾回收,它也永远不会在对象释放时缩小。图2.4在下一页上显示了大对象堆的示例。

现在假设我们有一个虚构的应用程序,可以创建大量的对象(> 85KB),因此大对象堆增长到200兆。现在假设我们有10个这样的应用程序实例正在运行...那么就分配了2000兆。那么这个内存直到进程关闭才不会被返回给操作系统...(这是我理解的)

我的理解有什么漏洞吗?我们如何回收各种LOH堆中未使用的内存;我们如何避免发生OutOfMemoryExceptions的完美风暴?

更新:从Marc的回答中,我想澄清一下,LOH对象没有被引用-大型对象是使用和丢弃的-然而,尽管堆相对空闲,但堆不会缩小。

更新#2:仅包含一个代码片段(夸张但我认为可以表达重点)... 我在我的机器上看到一个OutOfMemoryException,大约在虚拟内存达到1.5G标记时(另一台机器上为1.7G)... 来自Eric L.'s blog post,“进程内存可以被视为磁盘上的大型文件..”- 因此这个结果是意外的。在这种情况下,这些机器在硬盘上有几GB的可用空间。PageFile.sys操作系统文件(或相关设置)是否施加任何限制?
        static float _megaBytes;
        static readonly int BYTES_IN_MB = 1024*1024;

        static void BigBite()
        {
           try
           {
              var list = new List<byte[]>();
              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
        }
3个回答

6
首先需要澄清一下: 假设您正在以32位应用程序运行应用,那么您的进程可用的VA空间仅为2GB,如果启用了大地址空间开关,则为3GB。因此,即使您有巨大的页面文件,在32位进程中也无关紧要,而在64位中则重要,因为您有巨大的地址空间。
以下是需要回答的问题: 大小>85000字节的对象将分配在LOH上,注意它是85000字节而不是85K,并且这也是可能会发生变化的实现细节。 现在回到您的问题。 GC将取消使用的LOH段在以下两种情况下: 1-当机器上的内存压力很高(约95-98%)时 2-当无法满足新的分配请求时,它将取消使用LOH中未使用的页面
因此你会在这些情况下恢复内存。 在达到2GB限制之前就遇到OOM错误可能意味着存在VA碎片,VA碎片发生在您没有连续的VA地址空间来满足新的分配时,例如,您要求8KB的段,但在VA中没有2个连续的页面(假设页面大小为4K)
您可以在Windows调试工具中使用!vamap调试器扩展程序来验证这一点。
希望这可以帮助您 谢谢

3
如果LOH想要保留内存,那就由LOH自己决定。然而,不要忘记OutOfMemoryException是每个进程的问题,因为硬盘实际上是虚拟内存的限制因素。Eric Lippert最近在博客中讨论了这个话题。当然,这并不能防止由于所有页面导致性能变差...

1
将更新问题,此情况下LOH对象本身是使用即丢弃的。一旦它们完成了它们的任务,LOH对象已被释放,但堆不会缩小。考虑一个组件,在将对象发送到管道之前对其进行序列化的情况:在这种情况下,您分配了200兆字节作为字节数组,这些字节数组在数据传输后变成垃圾。 - Gishu
我不确定编辑是否有任何更改。我想要表达的是,拥有一个200MB的负担并不一定是问题。由于不活动,大部分数据很可能会从主内存中移出... - Marc Gravell
感谢提供链接。附上一段代码片段,让我更加困惑,但与引发本主题的问题相似。您的想法一如既往地受到赞赏。感谢您在这个论坛上所做的工作... - Gishu

2

如果您真的有这种分配模式,可以将大型对象移动到另一个应用程序域中-当您决定释放所有大型对象时,释放该应用程序域和该应用程序域的堆将被释放。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接