Фрагментация кучи больших объектов: CLR имеет какое-либо решение для этого?


Если ваше приложение таково, что оно должно делать много выделения/де-выделения объектов большого размера (>85000 байт), его в конечном счете вызовет фрагментацию памяти, и ваше приложение выбросит исключение из памяти.

Существует ли какое-либо решение этой проблемы или это ограничение управления памятью CLR?

6 7

6 ответов:

К сожалению, вся информация, которую я когда-либо видел, только предлагает управлять факторами риска самостоятельно: повторно использовать большие объекты, выделять их в начале, убедиться, что они имеют размеры, кратные друг другу, использовать альтернативные структуры данных (списки, деревья) вместо массивов. Это просто дало мне еще одну идею создания не фрагментирующего списка, который вместо одного большого массива разбивается на меньшие. Массивы / списки кажутся наиболее частыми виновниками IME.

Вот журнал MSDN статья о нем: http://msdn.microsoft.com/en-us/magazine/cc534993.aspx , но в нем не так уж много полезного.

Особенность больших объектов в сборщике мусора CLR заключается в том, что они управляются в другой куче. Сборщик мусора использует механизм, называемый "уплотнением", который в основном представляет собой фрагментацию и повторное связывание объектов в обычной куче. Дело в том, что поскольку "уплотнение" больших объектов (копирование и повторное связывание их) является дорогостоящей процедурой, GC предоставляет для них другую кучу, которая никогда не уплотняется.

Обратите также внимание, что выделение памяти смежный. То есть, если вы выделите Объект № 1, а затем объект № 2, Объект № 2 будет Всегда располагаться после Объекта № 1.

Это, вероятно, то, что заставляет вас выходить из воспоминаний.

Я бы предложил взглянуть на шаблоны проектирования, такие как Flyweight, Lazy Initialization и Object Pool.

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

Программа всегда бомбит на ООМ, потому что она запрашивает слишком большой кусок памяти, никогда потому, что она полностью исчерпала все адресное пространство виртуальной памяти. Вы можете возразить, что это проблема с фрагментированием LOH, так же легко утверждать, что программа использует слишком много виртуальной памяти.

Как только программа выходит за рамки выделения половины адресуемой виртуальной памяти (гигабайт), действительно пора либо рассмотреть возможность сделать свой код умнее, чтобы он не жрал так много воспоминаний. Или сделать обязательным условием 64-разрядную операционную систему. Последнее всегда дешевле. Он тоже не выходит из вашего кармана.

Is there any solution to this problem or is it a limitation of CLR memory management?

Нет другого решения, кроме пересмотра вашего дизайна. И это не проблема CLR. Обратите внимание, что проблема аналогична для неуправляемых приложений. Это объясняется тем, что слишком много памяти используется приложением одновременно и в сегментах, лежащих "невыгодно" в памяти. Если, тем не менее, нужно указать на какого-то внешнего виновника, я бы предпочел указать на диспетчер памяти ОС, который (конечно же) не сжимает адресное пространство виртуальной машины.

CLR управляет свободными областями LOH в свободном списке. В большинстве случаев это лучшее, что можно сделать против фрагментации. Но так как для действительно больших объектов количество объектов на сегмент LOH уменьшается - в конечном итоге мы получаем только один объект на сегмент. И где эти объекты расположены в пространстве виртуальной машины полностью зависит от диспетчера памяти ОС. Это означает, что фрагментация в основном происходит на уровне ОС, а не на уровне среды CLR. Это часто наблюдаемый аспект фрагментации кучи, и это не .NET виноват в этом. (Но это также верно, фрагментация также может происходить на управляемой стороне, как хорошо продемонстрировано в этой статье.)

Уже названы общие решения: повторное использование больших объектов. Я до сих пор не сталкивался ни с одной ситуацией, где бы это не могло быть сделано надлежащим образом. Тем не менее, это может быть сложно иногда и, следовательно, может быть дорого, хотя.

Мы обрабатывали изображения в нескольких потоках. Поскольку изображения были достаточно большими, это также вызвало исключения OutOfMemory из-за фрагментации памяти. Мы попытались решить эту проблему с помощью небезопасной памяти и предварительного выделения кучи для каждого потока. К сожалению, это не помогло полностью, так как мы полагались на несколько библиотек: мы смогли решить проблему в нашем коде, но не 3rd party.

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

Я видел в другом ответе, что LOH может уменьшаться в размере:

Большие массивы и фрагментация LOH. Что такое общепринятая конвенция?

" ... Теперь, сказав это, LOH может уменьшиться в размере, если область на его конце полностью свободна от живых объектов, поэтому единственная проблема заключается в том, если вы оставляете объекты там в течение длительного времени (например, продолжительность применения). ... "

Кроме того, что вы можете заставить вашу программу работать с расширенной памятью до 3 ГБ на 32-битная система и до 4 ГБ на 64-битной системе. Просто добавьте флаг / LARGEADDRESSAWARE в компоновщик или это событие post build:

Вызов " $(DevEnvDir)..\ tools\vsvars32.летучая мышь" editbin /LARGEADDRESSAWARE "$(TargetPath) "

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