Как стать автором
Обновить

Комментарии 50

Странно что эта память классифицируется как неуправляемая, хотя на самом деле это же просто свободная память в управляемой куче.


Действительно неуправляемая память — это та, что возвращается NativeMemory.Alloc и другими подобными методами.

Это, скорее всего — придержанная память: сегменты, некогда отведенные под управляемую память, которые были впоследствии целиком освобождены, но не возвращены ОС. Подробнее о механизме придерживание виртуальной памяти (VM Hoarding) можно прочитать в книге Конрада Кокосы "Управление памятью в .NET" в конце 5-главы (в русском издании 2020 года). Естественно классифицировать их именно такие сегменты именно как неуправляемую память: ни одной из управляемых куч эта память на текущий момент не отведена.
PS Сведений по деталям управления придержанной памятью именно в .NET 7.0 в книге Кокосы, естественно нет, так что предположение сделано из общих соображений. Но если автору статьи интересно, он может найти в этой книге описание инструментов, годных для того, чтобы выяснить это достоверно.

НЛО прилетело и опубликовало эту надпись здесь
Про VM Hoarding лучше почитать в блоге Microsoft у Maoni Stephens (Large Object Heap Uncovered (an old MSDN article).

Вы уверены, что это лучше — читать статью написанную в 2008 году и переопубликованную в 2016?
PS Никаких заметных на глаз помарок перевода книги Кокосы на русский я не помню. Но вообще-то соавторы перевода написали тут на Хабре статью про свой труд, так что недостатки их перевода лучше, наверное, обсудить с ними.
Естественно классифицировать их именно такие сегменты именно как неуправляемую память: ни одной из управляемых куч эта память на текущий момент не отведена.

Нет, не естественно. Среда исполнения знает про эту память и может выделить её управляемой куче при необходимости, т.е. среда может этой памятью управлять.

Тем не менее, в настоящий момент эта память управляемой куче не выделена. А в описании утилиты DotMemory (см. цитату в статье), явно написано, что утилита считает такую память — за пределами управляемой кучи — неуправляемой (хотя сама CLR, естественно, этой памятью управляет и может вернуть эту память ОС — что она и делает в дальнейшем, когда в системе возникает спрос на виртуальную память). Короче, вопрос IMO чисто терминологический, все пожелания — авторам утилиты.
PS А так-то при необходимости среда исполнения может управляемой куче выделить и передать память, которую она получит от ОС, а не только ту, которую она придержала.

А в описании утилиты DotMemory (см. цитату в статье), явно написано, что утилита считает такую память — за пределами управляемой кучи — неуправляемой

Вот именно к этому у меня и претензия. Какого хрена утилита DotMemory считает такую память неуправляемой?

Емнип в GC Server по куче на каждое ядро (что является ныне дефолтом) и "освобождение" кучи (или переезд) не есть освобождение памяти выделенной ОС процессу. Быстро проверить – включить в конфиге GC Workstation

VMMap для быстро проверить, что же там с памятью https://learn.microsoft.com/en-us/sysinternals/downloads/vmmap

Testlimit – создать дефицит памяти в ОС и глянуть, как себя поведет процесс (возвращает память = у вас нет проблемы) https://learn.microsoft.com/en-us/sysinternals/downloads/testlimit

+Просто вызвать gc.collect недостаточно, там 4 аргумента, + в гц сеттингс отдельно для loh и коллекта был параметр + эвейтифинализаторов и повторный вызов //сорян, без кода, я с телефона

Не совсем понял, что значит «Емнип в GC Server по куче накажите ядро»

В dotnet есть два вида garbage collector

Может и больше, ибо ранее заявляли, что будет и пользовательская поддержка

Про ядра – буквально по куче на ядро, не больше, не меньше, но для гц сервер

Спасибо. Да, читал об этом, но не особо углублялся

Конрада Кокосу советовал бы прочитать. очень много прикладных вещей относительно сборщика там есть.

Круто, спасибо за рекомендацию! Сложно читается?

В целом чтиво не самое простое, но написано все понятно если не отвлекаться и вдумчиво читать то нормально идет.

Ну понятно, о таком совсем просто вряд ли получится написать. Ещё раз спасибо!

А где самый главный вывод? Как не ограничивать приложение в памяти но заставить приложение отдать всю ненужную ему память когда интенсивная нагрузка закончилась?

Из всех проведённых экспериментов ясно, если приложение используется достаточно активно — то неуправляемая память не будет занята процессом на протяжении долгого периода времени и простаивать без дела. Как только либо тому же, либо другому процессу понадобится память, наше приложение не будет очень жадничать и начнёт его освобождать. 

Основная идея, если приложение не отдаёт память - значит оно никому особа и не нужна. Насильно у вас нет доступа заставить приложение вернуть всю не нужную память (круто, если бы кто-то пришёл и сказал, что это не так).

Если критично, чтобы приложение не жрало много памяти - то это уже дело реализации.

Мы никак не стали это исправлять, память в итоге всё равно освобождается. Я постарался показать это в описанных примерах.

Это создаёт проблемы при мониторинге, черт его знает сколько этой памяти свободно внутри этого приложения. Мониторинг оперативы и так дело сложное, а так ещё хуже становится.

Согласен, но мы не придумали как это можно решить. Для нас это был редкий случай, где у клиента была очень сложная конфигурация, поэтому пока оставили так.

Можно замутить знатный костыль - по завершении операции запускать процесс, который будет раздуваться по памяти и умирать. Тогда он отожмёт ненужное и освободит по завершении

Главное попутно ничего не сломать ?

Проще для тяжёлых по памяти операций запускать отдельный процесс.

А самое веселье начинается, когда когда нет ярко выраженного расчета и его завершения. Когда сборщик мусора на пиковых загрузках просто не успевает вызывать деструкторы явно не удаленных объектов.

И пару раз нарвавшись на такие проблемы, сразу приучаешься к явному вызову Delete() для независимости от сборщика мусора, а что-то вроде Clear() используешь только тогда, когда это действительно нужно.

Деструкторы вообще должны использоваться только когда без них не обойтись, и только для небольших объектов (именно по этой причине).


Явный же вызов чего-то вроде Delete() выглядит очень странно, потому что уже есть Dispose() и using.

Вот после такого подхода и пришлось рефакторить почти весь код АСКУЭ, как только вместо района попробовали запустить область. На пиках сборщик мусора просто не успевал сам вызывать деструкторы. После рефакторинга с Delete, пик потребляемой памяти снизился почти в пять раз. То есть k8s хватило четверти терабайта оперативки, вместо терабайта.

Неужели Вы считаете, что если Вы явно не вызвали Delete, его не вызовет потом сборщик мусора? Вот только это потом может быть уже тогда, когда все станет жутко тормозить на свопе.

О каком вообще Delete() вы, блин, говорите? Что этот Delete такого магического может сделать? В .NET не существует никакого Delete().

Серверный режим сборки мусора пробовали включать?

Сейчас не вспомню уже, но если вам интересно могу проверить

О каком именно Clear() вы говорите?

Об упомянутом в статье, который, по факту, вызывает Dispose(), а не Delete() на дочерние объекты, оставляя необходимость вызова Delete() уже сборщику мусора.

полезная статья, спасибо)))!!!

НЛО прилетело и опубликовало эту надпись здесь

А чем ждут то не угодил? ?

Спасибо за упоминание VMMap ?

Утечки памяти были банальные, мы подписались на событие а при удалении объекта, забывали отписаться. Эти утечки памяти не влияют на данную статью, к тому же не хотелось её делать слишком большой.

НЛО прилетело и опубликовало эту надпись здесь

В .NET 7 завесли новый GC основанный на сегментах и регионах. Возможно это влиет на стратегию возврата памяти OC. В этой статье рассказано как происходит decommit памяти в новом GC

Посмотрю, спасибо

Есть настройка System.GC.RetainVM, которая определяет, что делает сборщик мусора со свободной памятью, придерживает или возвращает обратно ОС.

Проверю, спасибо ?

Проверил. Эти настройки как-то особо не влияют в данном случае. System.GC.RetainVM по-умолчанию - false, поэтому память по-умолчанию должна отдаваться операционной системе

https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector

RetainVM: true
RetainVM: true
RetainVM: false
RetainVM: false

как по итогу решилась то проблема? Или так и оставили у клиента?))

Мы решили, что это не такая уж проблема, потому что, если системой пользоваться - то память освободится, память не освобождается только в состоянии простоя.

Не C# , но всё же об этом. Пожалуйста не делайте как chrome.

Посмотрите, свободно 30 ГБ ФИЗИЧЕСКОЙ памяти, но при открытие любого сайта , вылетает out of memory :D

Это как так? Не сталкивался с этим

Встречал такую проблему на сайте озон, повлялось через длительное использование сайта. Решалась перезапуском браузера. Проблема явно вызвана утечкой памяти в js на странице, к памяти системы она не имеет прямого отношения.

Не уверен что тут именно хром виноват, у винды самой по себе есть с памятью приколы.


Например, большая часть памяти у вас забита файловым кешем. Иногда винда почему-то начинает считать этот кеш очень важным и отказывается выгружать его по требованию.


А возможно, что закончился вообще какой-нибудь ограниченный пул памяти под растровые изображения.

У меня в хроме вкладка падала по Out of Memory, когда я гигантскую таблицу (200+ столбцов, 1000+ строк) отправлял на принтер.

Вообще Станислав Сидристый на каждом своем выступлении на HighLoad/DotNext так или иначе затрагивает работу с памятью. Из последнего, как они микросервисы в один дотнет-процесс объединяли, чтобы память как раз общей была. Плюс есть книга у него на гитхабе по результатам анализа работы CLR/GC по исходникам, правда в полузаброшенном состоянии. В общем, это достаточно известное поведение, если вообще не особенность работы ОС, т.к. по документации происходит вызов VirtualFree.

Рассказали бы лучше, как так у вас в веб-приложении единоразово аж 8 гигабайт памяти выгружается, что за юз кейс такой. Зачем столько объектов, что с ними потом происходит, за сколько это отрабатывается - это же какой-то веб-запрос? Если это отчет, то сколько результирующий файл весит? Гигабайты? Почему бы не использовать IAsyncEnumerable и какой-то пулинг? Или они и так есть? Все это было бы очень интересно узнать, интересная задачка же :)

Спасибо большое за ссылки, нужно будет выкроить время почитать.

Рассказали бы лучше, как так у вас в веб-приложении единоразово аж 8 гигабайт памяти выгружается, что за юз кейс такой.

Тут не могу всё раскрывать. Но если в двух словах - мы позволяем пользователю описывать визуально бизнес процесс визуально, диаграммами. И также позволяем его отлаживать, прямо как в дебагере. Отладчик не очень оптимально работает сейчас, он хранит все значения для каждого шага процесса. У пользователя был огромный бизнес процесс, с множеством циклов. И так как мы храним все значения дебагера, это требовало 8 ГБ оперативки.

В итоге мы перестали хранить все данные, для циклов, храним только значения для 5 операций. И теперь потребляется все 1 ГБ оперативки.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории