大对象堆碎片

我正在使用的C#/。NET应用程序正遭受缓慢的内存泄漏。我已经将CDB与SOS一起使用来尝试确定正在发生的事情,但是数据似乎没有任何意义,因此我希望你们中的一个以前可能已经经历过这种情况。


该应用程序在64位框架上运行。它正在不断地计算数据并将其序列化到远程主机,并且相当大地达到了大对象堆(LOH)。但是,我希望大多数LOH对象都是瞬态的:一旦计算完成并将其发送到远程主机,就应该释放内存。但是,我看到的是大量(活动的)对象数组与空闲的内存块交错,例如,从LOH中获取随机段:


0:000> !DumpHeap 000000005b5b1000  000000006351da10

         Address               MT     Size

...

000000005d4f92e0 0000064280c7c970 16147872

000000005e45f880 00000000001661d0  1901752 Free

000000005e62fd38 00000642788d8ba8     1056       <--

000000005e630158 00000000001661d0  5988848 Free

000000005ebe6348 00000642788d8ba8     1056

000000005ebe6768 00000000001661d0  6481336 Free

000000005f214d20 00000642788d8ba8     1056

000000005f215140 00000000001661d0  7346016 Free

000000005f9168a0 00000642788d8ba8     1056

000000005f916cc0 00000000001661d0  7611648 Free

00000000600591c0 00000642788d8ba8     1056

00000000600595e0 00000000001661d0   264808 Free

...

显然,如果我的应用程序在每次计算过程中都创建了长寿命的大对象,我希望情况会如此。(这样做是可以的,我接受会有一定程度的LOH碎片,但这不是问题所在。)问题是您在上述转储中看到的对象数组非常小(1056字节),我在代码中看不到被创建,并以某种方式保持根源。


还要注意,转储堆段时CDB不会报告类型:我不确定这是否相关。如果我转储标记的(<-)对象,CDB / SOS会很好地报告它:


对象数组的元素都是字符串,从我们的应用程序代码中可以识别出这些字符串。


另外,由于!GCRoot命令挂起并且再也没有回来,所以我找不到他们的GC根目录(我什至试图将其放置一夜)。


因此,如果有人能解释为什么这些小的(<85k)对象数组最终出现在LOH上,我将不胜感激:.NET在什么情况下会在其中放置一个小的对象数组?而且,有人碰巧知道确定这些对象根源的另一种方法吗?


更新1


我昨天晚些时候提出的另一种理论是,这些对象数组起初很大,但是已经缩小了,留下了内存转储中明显的可用内存块。使我感到怀疑的是,对象数组总是看起来长1056字节(128个元素),引用的长度为128 * 8,开销为32字节。


想法是,库中或CLR中的某些不安全代码可能破坏了数组头中元素数量的字段。我知道远射...


应用程序首先在循环中创建和取消引用唯一的字符串。这只是为了证明在这种情况下内存不会泄漏。显然,它不应该也不应。


在第二个循环中,创建并插入了唯一的字符串。此操作将它们根植在实习表中。我没有意识到实习表是如何表示的。看起来它由在LOH中创建的一组页面(由128个字符串元素组成的对象数组)组成。这在CDB / SOS中更为明显:


请注意,对象数组的大小为528(而不是1056),因为我的工作站是32位,而应用程序服务器是64位。对象数组仍为128个元素长。


因此,这个故事的寓意是要非常小心地进行实习。如果未知您正在实习的字符串是有限集的成员,则您的应用程序将因LOH的碎片而泄漏,至少在CLR版本2中会这样。


在我们的应用程序的情况下,反序列化代码路径中存在通用代码,可在解组期间实习实体标识符:我现在强烈怀疑这是罪魁祸首。但是,开发人员的意图显然是好的,因为他们想确保如果对同一实体进行多次反序列化,则只有一个标识符字符串实例将保留在内存中。


呼啦一阵风
浏览 525回答 4
4回答

富国沪深

在阅读有关GC如何工作的描述以及关于寿命长的对象如何在第二代中结束的部分以及LOH对象的收集仅在完全收集时发生-与第二代的收集一样,这个想法浮现在脑海。 ..为什么不将第二代和大型对象放在同一堆中,因为它们将被收集在一起?如果这是实际发生的情况,那么它将解释小物体如何最终与LOH并存-如果它们的寿命足够长,可以在第二代中终结。因此,您的问题似乎很好地反驳了我的想法-这将导致LOH分散。摘要:你的问题可以通过蕙解释和第2代共享同一个堆区,尽管这绝不是证明,这样的解释。更新:!dumpheap -stat几乎所有的输出使这一理论无所作为!第二代和LOH有自己的区域。

慕运维8079593

很好的问题,我通过阅读问题中学到了。我认为反序列化代码路径的其他部分也正在使用大对象堆,因此会产生碎片。如果所有琴弦都在同一时间被扣押,我想你会没事的。鉴于.net垃圾收集器的性能如何,仅让反序列化代码路径创建普通的字符串对象就足够了。在需求得到证实之前,不要做任何更复杂的事情。我最多只能看看保留您所看到的最后几个字符串的哈希表,然后再使用它们。通过限制哈希表的大小并在创建表时传递该大小,可以停止大多数碎片。然后,您需要一种方法,从哈希表中删除您最近未看到的字符串,以限制其大小。&nbsp;但是,如果反序列化代码路径创建的字符串寿命很短,那么您将不会获得太多收益。
打开App,查看更多内容
随时随地看视频慕课网APP