【问题标题】:Is There a .Net Memory Profiler that will track all allocations on the Large Object Heap? [closed]是否有一个 .Net 内存分析器可以跟踪大对象堆上的所有分配? [关闭]
【发布时间】:2012-04-12 07:08:47
【问题描述】:

我尝试过的大多数 .NET 内存分析器都允许您拍摄内存快照。

但是,我正在尝试诊断一个问题,即最终分配给 .NET 的大量内存被 ANTS 分析器指示为“空闲”。 (我已经用 Mem Profiler 和 CLR profiler 等其他分析器确认了这个问题。

ANTS 显示我有大量内存碎片(100% 的可用内存,最大块为 150MB。)堆中所有对象的总大小为 180MB。我有 553 MB 分配给 .NET,152 MB 分配给“非托管”。

但是,大对象堆 (LOH) 的大小仅为 175kb。这是那里分配的对象的实际大小。我不会永久分配最终在 LOH 上的任何对象。

因此,我的问题是,在某些地方,我怀疑我以某种方式分配了大型对象(超过 LOH 的 85k 限制)然后处置它们。

我正在从数据库(Oracle、Sql Server)中读取大量数据(此处估计为数 MB),将这些数据复制到内存中的对象数组,并将数据处理为索引(数组、字典等)更容易搜索/过滤/处理。

我的猜测是,数据读取器暂时分配了大量空间。但是,我没有一个很好的方法来暂停程序并拍摄内存快照。

我想要的是一个分析器,它可以跟踪 LOH 上分配的每个对象,这样我就可以找出导致 LOH 碎片和过多内存使用的原因(内存没有返回到操作系统,所以看起来我的进程需要 1GB 的内存来存储 200MB 的分配对象。)我猜内存没有返回,因为 LOH 没有被压缩,所以我一辈子都被这些内存卡住了我的进程,可能是几周(它作为 Windows 服务运行。)

编辑:我的问题是我的 .NET 应用程序使用了大量我无法追踪的内存。

Edit1:我使用了 Visual Studio 内存分析器。虽然它确实告诉我所有实例化的对象、有多少、总字节数等,但我没有提示我为什么最终会得到这么多可用内存。我唯一的提示/线索是 ANTS 告诉我的内容:“内存碎片限制了可以分配的对象的大小。”而且我有很多未使用的分配给 .NET 的未使用内存。

Edit2:更多分析显示我在 LOH 上分配了一些短暂的大对象。但是,在 LOH 上分配的总量永远不会超过 3 到 4 MB。然而,在这段时间里,私有字节猛增一倍和三倍,而我实际分配的对象(在所有堆上)的大小仅略有增长。例如,所有堆中的字节为 115MB,但我的私有字节超过 512 MB。

ANTS 清楚地告诉我,我遇到了内存碎片问题。原来我在 LOH 上创建了短暂的对象。但是,这些对象的总和永远不会超过 3 或 4 MB。所以这些短命的大物体(看起来像?)正在将 LOH 分解成碎片。

回应 Eric Lippert 和迪斯尼乐园停车场的类比(这很棒)。

就像有人在某个地方停了几分钟,然后就离开了。然后保留那个位置(没有其他人可以在那里停车),直到我重新铺设停车场!

当 Visual Studio 警告我内存使用情况并建议切换到 x64 时,我开始对此进行调查。 (我忘记了警告号码,快速谷歌没有找到它)。所以切换到 x64 可以缓解眼前的问题,但不能解决根本问题。

就像我有一个可以停 1000 辆汽车的停车场,但是当我把 100 辆汽车放进去后,我的停车服务员却在尖叫它已经满了……

幸运的是,我有一个庞大的 VMware 集群可供我使用,并且管理员很了解。我被分配了 8 个 CPU 和 8 GB 内存。所以就问题而言,我可以处理它,我只是向它投入资源。此外,(正如我上面所说)我切换到 x64 一段时间,因为 Visual Studio 一直在提醒我警告但是,我想弄清楚它在 LOH 上分配了什么,看看我是否可以减轻这个堆碎片一些小的代码更改。考虑到我可以为此投入资源,这可能是愚蠢的差事。

应用程序运行良好,速度很快,偶尔会出现 GC 暂停。但大多数情况下我可以忍受这种情况,我只想知道是什么物体造成的。我的怀疑是一些我还没有找到的短命字典。

Edit3http://msdn.microsoft.com/en-us/magazine/cc188781.aspx

ObjectAllocatedByClass 不跟踪大对象的分配 堆,但 ObjectAllocated 可以。通过比较来自的通知 二、有进取心的人应该能猜出里面是什么 大型对象堆,而不是普通托管堆。

所以看起来这个可以完成。但是,我的 C++ 技能很难深入研究(如果我有更多时间,可能会在未来某个时候)。我希望探查器能提供开箱即用的功能。

【问题讨论】:

  • 您的应用程序是否使用字符串实习?
  • 那么具体是什么问题?是你的虚拟内存用完了,还是工作集太大而你正在破坏页面文件,还是什么?
  • @EricLippert 问题是我的应用程序占用了内存,我不知道为什么。
  • 使用大量内存的应用程序本身不是问题;很明显,这是一个关注。但是,如果您有足够的虚拟内存并且没有破坏磁盘,那么使用用户会注意到的大量内存并没有实际的问题。只解决你实际遇到的问题。
  • 在我的例子中,将应用程序置于 GCServer 模式会导致为 .NET 分配大量“可用空间”。原因是 gcserver 模式为每个逻辑处理器创建堆。我有 8 个逻辑处理器,因此 .NET 框架将数据分配到 8 组堆上,我确信这会导致“过度分配”,等等。一旦我关闭 gcserver 模式,就会发生更少的“过度分配”。

标签: c# .net memory-management memory-leaks


【解决方案1】:

经过试验,当 GC 从一代中删除事物时,我能够得到通知,但在将其放入其中时却没有。

由于 LOH 不是特定于生成的,并且我无法访问特定事件以通知 LOH 插入,因此我可以提供的唯一选择是调试应用程序,进行故障转储 - 或者 - 在本地运行并使用WinDBG。你可以这样做:

  1. 下载 Windows SDK 7
  2. 将 %Microsoft_NET_Framework%\sos.dll 复制到 WinDBG 目录
  3. 在 WinDBG 中单击文件 -> 打开可执行文件 -> 指向您的可执行文件
  4. 在底部的命令栏中输入g (Go)
  5. 监控内存,当你想分析它时,去WinDBG -> Debug menu -> Break
  6. 输入 load .sos - 加载 .NET 扩展
  7. 键入!dumpheap -min 85000 - 这将列出应该驻留在LOH上的大型对象
         Address               MT     Size
0000000012a17048 000007fee7ae6ae8   400032     
0000000012a78b00 000007fee7ae6ae8   400032     
0000000012ada5b8 000007fee7ae6ae8   400032     
0000000012b3c070 000007fee7ae6ae8   400032     
0000000012b9db28 000007fee7ae6ae8   400032

接下来,我们需要逐一检查并找出其中的内容。

  1. 将第一列(对象地址)复制到剪贴板中
  2. 输入!do <paste from clipboard>
  3. 这将列出对象的内容、类型和大小。
CLR Version: 4.0.30319.261
SOS Version: 4.0.30319.239
Name:        System.String
MethodTable: 000007fee7ae6ae8
EEClass:     000007fee766ed68
Size:        400026(0x61a9a) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      8470737076787475867884758166807183888774746571677189..
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fee7aec9d0  4000103        8         System.Int32  1 instance           200000 m_stringLength
000007fee7aeb510  4000104        c          System.Char  1 instance               38 m_firstChar
000007fee7ae6ae8  4000105       10        System.String  0   shared           static Empty
                                 >> Domain:Value  000000000055fe50:0000000002a11420 <<

您要查找的行是:

Size:        400026(0x61a9a) bytes
String:      8470737076787475867884758166807183888774746571677189..
  1. 为每个对象执行此操作

(但是,我假设它是一个字符串,因此请检查“名称”属性以确定。它可能是一个数组。)

【讨论】:

    【解决方案2】:

    天哪 - @EricLippert 是问题 - 我知道我错了 - 但这里是 :-)

    LOH 只会存储大小大于 85k 的特定对象。我从经验中发现这通常是字符串 - XML 或全局变量和/或静态变量中的大型可枚举类型。

    如果您的 LOH 很小,则您必须将其他对象存储在内存中,这些对象很可能最终成为第 2 代。这些对象可能是全局变量,例如在应用程序生命周期中的数组、字典。

    所以要快速检查的第一件事是检查在应用程序的整个生命周期中是否存在任何变量?这些有成千上万的条目吗?

    其次,.NET 具有针对每一代大小的内存大小的性能计数器统计信息。计数器可以直接指向应用程序,因此这是一个很好的下一步。

    最后,GC 只会在需要时清除内存。所以是的,如果 GC 不觉得需要(内存压力)清除任何东西,它可能会保持相同的大小。您可以强制垃圾收集,但通常建议让 GC 完成其工作。

    我问了一个关于why a string was being stored in my application for so long. 的类似问题我正在使用 WinDBG 分析我通过转到任务管理器 -> 进程 -> 右键单击​​ -> 创建故障转储来强制执行的故障转储。然后将其加载到 WinDBG (Windows SDK 7)。

    希望该链接能够为您提供更多指导,帮助您找出问题的根源(双关语)。

    然后向 OP 提出一个问题:这会严重影响您的服务器,还是您只是好奇为什么内存会持续存在?

    【讨论】:

    • Dominic,感谢您提供的信息。我的问题是,我 存储数据。这些项目只是暂时在 LOH 上,但分配的内存会永远存在。
    • 明白了。我要说的真的是放心,当需要删除内存时,GC会清除它。很抱歉继续我的另一篇文章,但我有一个非常相似的问题。当我创建故障转储时,WinDBG 确实在 LOH 上列出了该项目,因此我能够在那一刻找出它包含的内容。如果您可以让应用程序在本地运行,您可以将 WinDBG 附加到它并暂停程序并自己检查 LOH。
    • 实际上,根据我的研究,LOH 永远不会被压缩。所以是的,如果我需要它(我不需要),我可以稍后使用该内存,但它只是徘徊,并且再次根据我的研究,不会发布到操作系统。 connect.microsoft.com/VisualStudio/feedback/details/521147/…
    • 直到您调用 System.GC.Collect(GC.MaxGeneration) 或操作系统指示存在高内存压力,它才会被压缩,所以这就是为什么 .NET 只是让它坐在那里。它在 CPU 周期和内存管理方面很昂贵,但如果它让你在晚上睡觉,那就试试吧 ;-)
    • @DominicZukiewicz,也许你混淆了这些条款。您命名的是收集 LOH 的情况。但据我所知,在当前实现中,它永远不会压缩(即其中的对象被移动)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 1970-01-01
    相关资源
    最近更新 更多