После исследования проблема заключается в фрагментации кучи из-за закрепленных буферов .Я объясню, как исследовать и какие буферы закреплены.
Все профилировщики, которые я использовал, согласились сказать, что большая часть кучи бесплатна.Теперь мне нужно было посмотреть на фрагментацию.Я могу сделать это с WinDbg, например:
!dumpheap -stat
Затем я посмотрел на раздел «Фрагментированные блоки больше ...».WinDbg говорит, что объекты лежат между свободными блоками, что делает уплотнение невозможным.Затем я посмотрел на то, что удерживает эти объекты, и, если они закреплены, вот, например, объект по адресу 0000000bfaf93b80:
!gcroot 0000000bfaf93b80
Отображается граф ссылок:
00000004082945e0 (async pinned handle)
-> 0000000535b3a3e0 System.Threading.OverlappedData
-> 00000006f5266d38 System.Threading.IOCompletionCallback
-> 0000000b35402220 System.Net.Sockets.SocketAsyncEventArgs
-> 0000000bf578c850 System.Net.Sockets.Socket
-> 0000000bf578c900 System.Net.SocketAddress
-> 0000000bfaf93b80 System.Byte[]
00000004082e2148 (pinned handle)
-> 0000000bfaf93b80 System.Byte[]
Последние двалинии говорят вам, что объект закреплен.
Закрепленные объекты - это буферы, которые невозможно переместить, поскольку их адрес используется совместно с неуправляемым кодом.Здесь вы можете догадаться, что это системный уровень TCP.Когда управляемому коду необходимо отправить адрес буфера во внешний код, ему нужно «закрепить» буфер, чтобы адрес оставался действительным: ГХ не может его переместить.
Эти буферы, хотя и очень малыЧасть памяти делает уплотнение невозможным и, таким образом, вызывает большую «утечку» памяти, даже если это не совсем утечка, а скорее проблема фрагментации.Это может произойти на LOH или на кучах поколений точно так же.Теперь вопрос: что заставляет эти закрепленные объекты жить вечно: найдите основную причину утечки, которая вызывает фрагментацию.
Подобные вопросы можно прочитать здесь:
Примечание: основная причина была в сторонней библиотеке AerospikeClient с использованием .NET async Socket API, который известен тем, что закрепляет отправленные ему буферы .Хотя AerospikeClient правильно использовал буферный пул, буферный пул был воссоздан при повторном создании их клиента.Поскольку мы воссоздали их клиента каждый час, а не создавали один навсегда, пул буферов был воссоздан, что привело к увеличению числа закрепленных буферов, что, в свою очередь, привело к неограниченной фрагментации.Что остается неясным, так это то, почему старые буферы никогда не открепляются, когда передача завершена или, по крайней мере, когда их клиент удален.