如何解决Gen2堆碎片问题

7

我正在运行一款处理HTTP请求的C#应用程序。最近我发现它所占用的内存比我预期的还要多。我获取了一些转储文件,通过Windbg打开后发现大部分内存都被标记为“Free”:

!dumpheap -stat
...
00007ffde4783630   681599     65433504 System.Threading.Tasks.TaskFactory+CompleteOnInvokePromise
00007ffde47cc988   167885     76872908 System.Byte[]
00007ffde47c6948   521353     80352802 System.String
0000007e3a16c2d0  1870425   1415374334      Free

因此,这个转储文件大约有3GB大小,其中约有一半是空闲内存。查看堆栈时,我看到了这个:
!heapstat
Heap             Gen0         Gen1         Gen2          LOH
Heap0        82248472      7354560    987275056    178834656
Heap1        93146552      6382864    857470096    129435960
Total       175395024     13737424   1844745152    308270616

Free space:                                                 Percentage
Heap0        40969256       146456    640426720     54829792 SOH: 63% LOH: 30%
Heap1        75943736        94448    550812312     54825216 SOH: 65% LOH: 42%
Total       116912992       240904   1191239032    109655008

我的小对象堆非常碎片化,特别是Gen2。在服务器上,我可以看到Gen2集合正在发生(使用性能计数器),但即使它们是,看起来Gen2堆也没有被压缩。即使服务器上只有1-2%的RAM可用,Gen2堆也不会被压缩。
对我而言,这似乎是因为堆被碎片化而导致的内存压力。然而,我无法弄清楚为什么会发生碎片化,或者为什么Gen2不能被压缩。一些空闲空间的大小为6MB,所以我认为它一定会将这些空间压缩掉。
有人可以给我一些想法,帮我找出为什么我的堆如此碎片化吗?我是否在错误的方向上努力?
任何帮助都将不胜感激,谢谢!
编辑1:
`!gchandles`的细分如下:
Handles:
Strong Handles:       4507
Pinned Handles:       58
Async Pinned Handles: 977
Ref Count Handles:    1
Weak Long Handles:    6087
Weak Short Handles:   724

请展示您的代码。 - mjwills
这两个链接有帮助吗?https://msdn.microsoft.com/zh-cn/library/system.runtime.gcsettings.largeobjectheapcompactionmode(v=vs.110).aspx?f=255&MSPPError=-2147217396 https://blogs.msdn.microsoft.com/mariohewardt/2013/06/26/no-more-memory-fragmentation-on-the-net-large-object-heap/ - mjwills
很遗憾,这是一个庞大的应用程序,不方便发布代码。我更多地是在寻找一般性的解决方法来尝试解决碎片化问题。我会阅读你发送的两篇文章,并回复你。 - shortspider
公平的呼叫 @shortspider。祝你解决问题顺利。 - mjwills
2
这是很多异步固定句柄。你的程序可能有很多活动套接字读取,一遍又一遍地使用相同的byte[]缓冲区。因此,缓冲区不断被固定,分配它的堆段无法被回收利用。请搜索“ .net http buffer pooling”以获取更多信息。 - Hans Passant
显示剩余2条评论
1个回答

0
下一步应该使用 !gchandles 并查找已钉住的 handle,这可能会识别出被锁定到特定内存位置的对象,因为一些原生代码 (例如 C++) 需要访问它。虽然垃圾回收可能会将对象在内存中移动,但 .NET 不会更新 C++ 指针,因此固定是唯一的选择。

即使服务器上只有 1-2% 的可用 RAM,gen2 堆也不会被压缩。

您在谈论物理 RAM。从操作系统的角度来看,我期望它尽可能好地利用可用资源,因此我期望 RAM 在任何时候都被 100% 使用。如果它“没有被使用”,我期望操作系统将其用于缓存。因此,物理 RAM 使用情况不能真正成为垃圾回收的指标。
此外,我不会太担心。如果内存未被 .NET 使用,操作系统会将其交换到磁盘上,从而释放物理 RAM 供其他用途使用。

我会运行!gchandles并在回到办公室后发布结果。我理解你的观点,即未使用的RAM是浪费的RAM,但我看到的是频繁的gen2 GC,这导致应用程序出现暂停。我认为正在发生的事情是因为物理RAM大部分被使用,GC更频繁地运行昂贵的gen2 GC以尝试释放一些RAM。然而,当我查看时,大部分gen2堆已经空闲且未被使用。这是错误的结论吗? - shortspider
添加了 !gchandles 的输出。我需要查看固定和异步固定句柄吗?我需要关注其他类型吗? - shortspider
@shortspider:目前,请参考Hans Passant的评论。我会尝试相应地编辑我的答案。 - Thomas Weller

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