Как решить проблему фрагментации кучи 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, она будет перенесена на диск операционной системой и, таким образом, освободит физическую память для других целей.