Как решить проблему фрагментации кучи Gen2

Я запускаю приложение C#, которое обслуживает HTTP-запросы. Недавно я заметил, что это занимает больше памяти, чем я ожидаю. Я взял несколько дампов, вытолкнул их в Windbg и обнаружил, что большая часть памяти была помечена как свободная:

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

Таким образом, дамп составляет ~3 ГБ, так что около половины его составляет свободная память. Глядя на кучи, я вижу это:

!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% оперативной памяти, куча gen2 не сжимается.

Мне кажется, что я испытываю это давление памяти, потому что куча фрагментирована. Однако я не могу понять, почему происходит фрагментация или почему gen2 не может быть уплотнен. Некоторые из свободных пространств имеют размер 6 МБ, поэтому я думаю, что они наверняка сожмут эти места.

Кто-нибудь может дать мне несколько идей о том, как понять, почему моя куча так фрагментирована? Я даже лаю здесь правильное дерево?

Любая помощь будет принята с благодарностью, спасибо!

РЕДАКТИРОВАТЬ 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

1 ответ

Следующим шагом будет использование !gchandles и искать закрепленные ручки. Это может идентифицировать объекты, которые привязаны к определенной позиции памяти, потому что некоторый нативный код (например, C++) должен получить к нему доступ. В то время как сборщик мусора может перемещать объекты в памяти, указатель C++ не будет обновляться.NET, поэтому закрепление является единственным вариантом.

Даже если на сервере доступно всего 1-2% оперативной памяти, куча gen2 не сжимается.

Вы говорите о физической памяти здесь. От операционной системы я ожидаю, что она использует доступные ресурсы настолько хорошо, насколько это возможно, поэтому я ожидаю, что ОЗУ будет использоваться на 100% все время. Если он "не используется", я ожидаю, что ОС будет использовать его для кэширования. Из-за этого использование физической памяти не может быть индикатором для сборки мусора.

Кроме того, я бы не слишком волновался. Если память не используется.NET, она будет перенесена на диск операционной системой и, таким образом, освободит физическую память для других целей.

Другие вопросы по тегам