【问题标题】:OutOfMemory, but no gcroots for many objectsOutOfMemory,但许多对象没有 gcroots
【发布时间】:2009-10-10 22:24:38
【问题描述】:

我们正在开发一个相当大的 Windows 窗体应用程序。在几个客户的计算机中,它经常因 OutOfMemory 异常而崩溃。在异常发生后(从 UnhandledException 处理程序调用 clrdump)获得应用程序的完整内存转储后,我使用“.NET Memory Profiler”和 windbg 对其进行了分析。

内存分析器在活动对象实例中仅显示 130MB。有趣的是,对于许多对象类型,已经显示出大量无法访问的实例(例如 22000 个无法访问的 Byte[] 实例)。在本机内存统计中,所有数据堆中的数据总计为 127MB(这还可以),但表示第 2 代堆中的 133MB 和大堆中的 640MB 无法访问(不好!)。

使用windbg分析转储时,确认了以上统计信息:

!dumpheap -stat
..... acceptable object sizes...
79330a00   467216     30638712 System.String
0016d488     4804    221756612      Free
79333470    27089    574278304 System.Byte[]

应用程序在运行期间确实使用了大量的短缓冲区,但不会泄漏它们。使用 !gcroot 测试许多 Byte[] 实例最终没有根。显然,这些数组中的大多数都是不可访问的,正如内存分析器所指示的那样。

为了确保一切正常,!finalizequeue 显示没有对象等待完成

generation 0 has 138 finalizable objects (18bd1938->18bd1b60)
generation 1 has 182 finalizable objects (18bd1660->18bd1938)
generation 2 has 75372 finalizable objects (18b87cb0->18bd1660)
Ready for finalization 0 objects (18bd1b60->18bd1b60)

并且还要检查本地终结器线程堆栈跟踪是否显示它没有被阻塞。

目前我不知道如何诊断 GC 不收集数据的原因(我相信它会很乐意,因为进程内存不足..)

编辑:根据下面的输入,我阅读了有关大型对象堆碎片的更多信息,似乎情况可能如此。

我已经看到一些建议为这种数据分配更大的内存块(在我的情况下是各种字节 [])并自己管理该区域的内存,但这似乎是一个相当老套的解决方案,而不是一个我希望解决一个不那么特别的桌面应用程序的问题。

碎片问题是由于 LOH 上的对象在存在期间没有重定位这一事实(至少这是许多 Microsoft 的博客中所说的)造成的,这是可以理解的,但一旦达到一定的内存压力似乎是合乎逻辑的,比如有OOM的威胁,应该进行重定位。

在完全相信碎片是原因之前唯一让我担心的是,LOH 上的这么多对象没有 gcroot 引用 - 这是因为即使是 LOH 垃圾收集也只是部分执行?

我很乐意为我指出任何有趣的解决方案,因为目前我所知道的唯一一个是一些预分配内存块的自定义管理。

欢迎任何想法。 谢谢。

【问题讨论】:

    标签: .net winforms windbg out-of-memory garbage-collection


    【解决方案1】:

    LOH 会出现碎片。 This article 提供分析和解决它的基本方向。
    也许您可以发布一些代码来显示这些 byte[] 缓冲区的“典型”用法?

    【讨论】:

    • 缓冲区从 20k 到几 MB 不等,通常由数据库提供程序创建以从 db 加载数据或由我们的代码创建,然后用来自套接字的数据填充它(数据大小是已知的,因此缓冲区以正确的大小定义,而不是增长)
    • 它们在运行时或设计时是否已知大小?看起来你必须找到一种方法来重新使用它们。
    • 大小在运行时确定 - 缓冲区存储电子邮件 MIME 部分,因此每个缓冲区的大小不同。我们可能会尝试重用我们自己的缓冲区,但也有来自提供者的缓冲区。但是,是的,目前我们将不得不尝试重用一些缓冲区。虽然它只会推迟问题,但我相信..
    • grepfruit,这是一个自相矛盾的领域。可能需要舍入 up 分配(当您需要 85kB 到 1MB 时分配 1MB)。您也可以在此处发布更具体的问题(如何优化 LOH)。
    • 问题是我仍然不能完全确定这是 LOH 碎片,还是只是问题。令我不安的是,LOH 上有这么多对象是未引用的,但它们并未标记为可用空间。因为从我读到的关于 gen#2 和 LOH 的内容中,始终会执行完整的垃圾回收——因此假设在 OOM 之前执行了回收,那么应该有最少的未引用对象。
    【解决方案2】:

    通常情况会有所不同。我们发现了一个用例,其中应用程序确实消耗了大量内存并最终会出现 OOM。在我们发现之前我们得到的转储中有什么奇怪的是有很多没有 gcroot 的对象 - 我不明白为什么它没有被释放并用于新的分配?然后我想到,当 OOM 发生时可能发生了什么 - 堆栈已展开,拥有内存的对象不再可访问,然后执行了转储。所以这就是为什么似乎有很多内存可以被 GCed。

    我在调试版本中所做的 - 检索 real 内存状态转储 - 是创建一个 Threading.Timer 来检查是否可以分配一些相当大的对象 - 如果它无法分配,这表明我们接近 OOM 并且是进行内存转储的好时机。代码如下:

    private static void OomWatchDog(object obj)
    {
     try                          
     {
       using(System.Runtime.MemoryFailPoint memFailPoint = 
              new System.Runtime.MemoryFailPoint(20))
       {
       }
     }
     catch (InsufficientMemoryException)
     {
       PerformDump();
     }
    }
    

    【讨论】:

      【解决方案3】:

      有时 Image.FromFile("a non-image file") 会抛出 OutOfMemoryException。零字节文件就是这样一种文件。

      【讨论】:

      • 感谢您的信息,我们以前也遇到过这个问题,所以很好。但是此时在分配过程中会抛出OOM,因此可能确实没有足够的连续空间。
      【解决方案4】:

      如果您认为 LOH 是问题所在,那么在 LOH 分配上设置一个断点可能会为您指明正确的方向。你可能会做这样的事情

      bp mscorwks!gc_heap::allocate_large_object "!clrstack;.echo *********分配大对象堆***********;g"

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-06-14
        • 1970-01-01
        • 1970-01-01
        • 2021-06-20
        • 1970-01-01
        • 1970-01-01
        • 2011-12-06
        相关资源
        最近更新 更多