none
ASP.Net Core 3.0 ограничение на размер в ОЗУ RRS feed

  • Вопрос

  • Здравствуйте.

    Подскажите, способ как можно ограничить приложение в объеме потребляемой памяти. Речь о том, что если создать базовое приложение ASP.Net Core 3.0 MVC и обновлять страницу, то размер занимаемой приложением памяти постоянно растет и почти не уменьшается. Вопрос как ограничить размер увеличения приложения в памяти, задав это ограничение из самого приложения?

    23 ноября 2019 г. 7:23

Ответы

  • Существует параметр конфигурации System.GC.HeapHardLimit для ограничения максимального размера управляемой кучи, возможно вам это нужно. Или просто периодически вызывать GC.Collect с четвертым параметром true, это вызовет сборку мусора и сжатие кучи, что должно освободить всю ненужную память. 

    • Помечено в качестве ответа Liliya Muray 25 ноября 2019 г. 12:42
    23 ноября 2019 г. 15:00

Все ответы

  • Такой функции нет и не может быть в ASP.NET, ведь ОЗУ управляет операционная система, а не фреймворк. Ограничить размер физической памяти процесса в Windows можно, например, с помощью функции SetProcessWorkingSetSizeEx. Но я думаю, вместо того, чтобы ограничивать память (что довольно бессмысленная затея для веб-приложения), нужно определить реальную проблему и решать ее. См. например Garbage Collection and Performance. Просто растущий размер Working Set - это не проблема, а нормальное состояние. Каждый запрос выделяет память под некоторые объекты, которые сразу не освобождаются, так как сборка мусора еще не запустилась. Эта память будет освобождена, когда она понадобится другим приложениям. Вот если не освобождается, а вместо этого падает с OutOfMemoryException, тогда нужно искать утечку или оптимизировать потребление памяти.
    23 ноября 2019 г. 10:34
  • Вопрос появился из-за того, что на хостинге мне разрешается тратить 1ГБ оперативы, когда мои приложения превышают этот размер их перегружают... Средства ОС в данном случае бессильны и недоступны... Вот и ищу способы, как заставить отдать приложение память, которая ему не особо нужна, чтоб не допустить общего перерасхода памяти.
    23 ноября 2019 г. 13:58
  • Существует параметр конфигурации System.GC.HeapHardLimit для ограничения максимального размера управляемой кучи, возможно вам это нужно. Или просто периодически вызывать GC.Collect с четвертым параметром true, это вызовет сборку мусора и сжатие кучи, что должно освободить всю ненужную память. 

    • Помечено в качестве ответа Liliya Muray 25 ноября 2019 г. 12:42
    23 ноября 2019 г. 15:00
  • Начала эксперименты со второго варианта. Я правильно поняла, что нужно вызывать команду с такими параметрами:

    GC.Collect(2, GCCollectionMode.Forced, true, true);
    Память уменьшается, но на крайне малую величину. Но следующий код GC.CollectionCount(*) - для каждого поколения увеличивается на единицу и со временем не уменьшается. Я что-то не правильно написала в параметрах GC.Collect?



    24 ноября 2019 г. 6:13
  • Опишите подробнее ситуацию:

    - Какая ОС

    - По какой характеристике оцениваете потребление памяти (рабочий набор, виртуальная память) и через какое ПО ее получаете

    - Сколько запросов вы выполняете перед запуском сборки мусора и какое при этом потребление памяти

    - До какого значения уменьшается потребление памяти после сборки мусора

    Да, я имел в виду именно такой вызов GC.Collect. Абсолютно правильно было бы использовать GC.MaxGeneration вместо константы 2, но это пока не важно. 

    24 ноября 2019 г. 7:40
  • Значение памяти выковыриваю таким кодом:

    public IActionResult PrivateMemorySize64() { using var proc = Process.GetCurrentProcess(); return Ok(proc.PrivateMemorySize64); }

            public IActionResult WorkingSet64()
            {
                using var proc = Process.GetCurrentProcess();
                return Ok(proc.WorkingSet64);
            }

    Для приложений получаю значения:

    PrivateMemorySize64=50,16МБ и WorkingSet64=82,55МБ
    PrivateMemorySize64=138,46МБ и WorkingSet64=67,47МБ

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

    PrivateMemorySize64=130,51МБ и WorkingSet64=58,41МБ

    Но к ним после вызова указанной вами команды оно не возвращается, скидывает максимум метр. А еще в подсчете объектов различный поколений прибавляется по одному объекту, вот код получения значения после вызова команды:

            public IActionResult Collect2()
            {
                GC.Collect(2,GCCollectionMode.Forced,true,true);
                using var proc = Process.GetCurrentProcess();
                return Ok(proc.PrivateMemorySize64);
            }

    24 ноября 2019 г. 9:43
  • 138 - 130 = 8 MB, это слишком мало. Когда выделено так мало памяти, судить о том, что там освобождается, а что нет, довольно трудно. Возможно, CLR решает, что при таком малом потреблении нет смысла что-то освобождать, так как память скоро понадобится снова. Кроме того, сам первый вызов GC.Collect может выделять память под что-то. Выполните больше запросов, чтобы количество неиспользуемой памяти было ощутимым: создайте скрипт, который будет отправлять запросы автоматически (с той частотой, которую позволяет хостинг, чтобы сайт не заблокировали) и оставьте его работающим на некоторое время. Затем померяйте эффект от GC.Collect при большом потреблении памяти.
    24 ноября 2019 г. 12:59
  • А почему вызов GC.Collect() с параметрами или без приводит к увеличению кол-ва на единицу для каждого поколения ( GC.CollectionCount(i), где i от 0 до 2 ) ?
    24 ноября 2019 г. 14:31
  • Потому что GC.CollectionCount возвращает количество прошедших сборок мусора (автоматически или вызовом GC.Collect) - оно и должно увеличиваться. К потреблению памяти оно не имеет отношения.
    24 ноября 2019 г. 15:18
  • Т.е.нормально что было 0:0:0 для поколений 0,1,2 а после вызова стало 1:1:1, а после 12 вызовов 12:12:12? Мне не кажется такое поведение нормальным.

    24 ноября 2019 г. 18:18
  • Еще раз: GC.CollectionCount возвращает количество прошедших сборок мусора. Если не было автоматических сборок, оно равно количеству запусков GC.Collect. Запуск GC.Collect без параметров запускает сборку для всех поколений. Какое другое поведение вы ожидали?
    25 ноября 2019 г. 3:07
  • Я ожидала следующее поведение: если нет объектов ни одного из поколений, то после сборки мусора их не должно появится. Если объекты были и они пережили сборку, то у них увеличивается номер поколения, но не количество. Просто не понятно как до сборки мусора не было объектов, а после сборки они объявились, причем ровно по одному в каждом поколении, и при каждой сборки счетчик каждого поколения прибавляет по одному объекту. Без принудительного сбора мусора кол-во всегда ноль, а после принудительной в каждом поколении добавляется по одному объекту. Т.е. после 39 принудительных сборок мусора у меня в каждом поколении по 39 объектов.
    25 ноября 2019 г. 5:28
  • Но это не "количество объектов", это "количество сборок мусора". Откуда вы взяли про количество объектов? Даже русская документация, хоть и машинный перевод, точно отражает суть: "Возвращает количество операций сборки мусора, выполненных для заданного поколения объектов.https://docs.microsoft.com/ru-ru/dotnet/api/system.gc.collectioncount?view=netcore-3.0

    Для получения количества объектов в поколении функции нет и никогда не было. Только профилировщики и счетчики производительности могут его показывать, и то обычно объем памяти по поколениям, а не количество объектов.

    25 ноября 2019 г. 6:34
  • Та не читала я документацию((( Просто коллекция и кол-во восприняла, как количество в коллекции.

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

    25 ноября 2019 г. 8:17
  • Прикрутила вызов GC по таймеру. Работает. По таймеру часть памяти возвращается в систему и приложение не раздувается.

    Спасибо за помощь!

    25 ноября 2019 г. 12:46
  • Для современных реалий 1 Gb памяти это мало. Тот же поток потребляет 1 МБ / 4 МБ памяти в зависимости от разрядности системы. Если у вас I/O операции синхронные, то достаточно 50-100 одовременных запросов в x64 процессе и вот вам 400 МБ памяти. Но всё это догадки. Реально же вам нужно найти возможность делать периодические дампы, а потом их анализировать. PerfView вам в помощь.

    Сделаем содержимое сообщества лучше, вместе!

    25 ноября 2019 г. 13:08
    Модератор
  • Для современных реалий 1 Gb памяти это мало. Тот же поток потребляет 1 МБ / 4 МБ памяти в зависимости от разрядности системы. Если у вас I/O операции синхронные, то достаточно 50-100 одовременных запросов в x64 процессе и вот вам 400 МБ памяти. Но всё это догадки. Реально же вам нужно найти возможность делать периодические дампы, а потом их анализировать. PerfView вам в помощь.
    Одновременных запросов минимум, сами приложения не большие. Вопрос в том, что пул приложений + сами приложения переползали границу в 1 Gb. На размер пула я повлиять не могу, но вот сократить объем самих приложений это более реальная задача. Частый вызов GC позволил возвращать уже не нужную в текущий момент память в ОС.
    25 ноября 2019 г. 13:41