Как я заставляю.NET настойчиво собирать "мусор"?

У меня есть приложение, которое используется в обработке изображений, и я выделяю обычно, выстраивает в 4000x4000 ushort размер, а также случайное плавание и т.п.. В настоящее время платформа.NET имеет тенденцию отказывать в этом приложении, по-видимому, случайным образом, почти всегда с из ошибки памяти. 32 МБ не являются огромным объявлением, но если.NET фрагментирует память, то очень возможно, что такие большие непрерывные выделения не ведут себя как ожидалось.

Существует ли способ сказать сборщику "мусора" быть более агрессивным, или дефрагментировать память (если это - проблема)? Я понимаю, что существуют вызовы GC.Collect и GC.WaitForPendingFinalizers, и я опрыснул их довольно подробно через мой код, но я все еще получаю ошибки. Это может быть, потому что я называю dll стандартные программы, которые используют собственный код много, но я не уверен. Я пробежался через тот код C++ и удостоверяюсь, что любая память, я объявляю, что удаляю, но тем не менее я получаю эти катастрофические отказы C#, таким образом, я вполне уверен, это не там. Интересно, могли ли вызовы C++ вмешиваться в GC, заставляя это оставить позади память, потому что это когда-то взаимодействовало с собственным вызовом - который возможен? Если так, я могу выключить ту функциональность?

Править: Вот некоторый очень определенный код, который вызовет катастрофический отказ. Согласно этому ТАК вопрос, я не должен избавляться от объектов BitmapSource здесь. Вот наивная версия, никакой GC.Collects в ней. Это обычно разрушает на повторении 4 - 10 из процедуры отмены. Этот код заменяет конструктора в пустом проекте WPF, так как я использую WPF. Я делаю дурацкость с bitmapsource из-за ограничений, которые я объяснил в своем ответе на @dthorpe ниже, а также требования, перечисленные в этом ТАК вопрос.

public partial class Window1 : Window {
    public Window1() {
        InitializeComponent();
        //Attempts to create an OOM crash
        //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
        int theRows = 4000, currRows;
        int theColumns = 4000, currCols;
        int theMaxChange = 30;
        int i;
        List theList = new List();//the list of images in the undo/redo stack
        byte[] displayBuffer = null;//the buffer used as a bitmap source
        BitmapSource theSource = null;
        for (i = 0; i < theMaxChange; i++) {
            currRows = theRows - i;
            currCols = theColumns - i;
            theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
            displayBuffer = new byte[theList[i].Length];
            theSource = BitmapSource.Create(currCols, currRows,
                    96, 96, PixelFormats.Gray8, null, displayBuffer,
                    (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            System.Console.WriteLine("Got to change " + i.ToString());
            System.Threading.Thread.Sleep(100);
        }
        //should get here.  If not, then theMaxChange is too large.
        //Now, go back up the undo stack.
        for (i = theMaxChange - 1; i >= 0; i--) {
            displayBuffer = new byte[theList[i].Length];
            theSource = BitmapSource.Create((theColumns - i), (theRows - i),
                    96, 96, PixelFormats.Gray8, null, displayBuffer,
                    ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            System.Console.WriteLine("Got to undo change " + i.ToString());
            System.Threading.Thread.Sleep(100);
        }
    }
}

Теперь, если я являюсь явным в вызове сборщика "мусора", я должен перенести весь код во внешний цикл для порождения катастрофического отказа OOM. Для меня это имеет тенденцию происходить вокруг x = приблизительно 50:

public partial class Window1 : Window {
    public Window1() {
        InitializeComponent();
        //Attempts to create an OOM crash
        //to do so, mimic minute croppings of an 'image' (ushort array), and then undoing the crops
        for (int x = 0; x < 1000; x++){
            int theRows = 4000, currRows;
            int theColumns = 4000, currCols;
            int theMaxChange = 30;
            int i;
            List theList = new List();//the list of images in the undo/redo stack
            byte[] displayBuffer = null;//the buffer used as a bitmap source
            BitmapSource theSource = null;
            for (i = 0; i < theMaxChange; i++) {
                currRows = theRows - i;
                currCols = theColumns - i;
                theList.Add(new ushort[(theRows - i) * (theColumns - i)]);
                displayBuffer = new byte[theList[i].Length];
                theSource = BitmapSource.Create(currCols, currRows,
                        96, 96, PixelFormats.Gray8, null, displayBuffer,
                        (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
            }
            //should get here.  If not, then theMaxChange is too large.
            //Now, go back up the undo stack.
            for (i = theMaxChange - 1; i >= 0; i--) {
                displayBuffer = new byte[theList[i].Length];
                theSource = BitmapSource.Create((theColumns - i), (theRows - i),
                        96, 96, PixelFormats.Gray8, null, displayBuffer,
                        ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8);
                GC.WaitForPendingFinalizers();//force gc to collect, because we're in scenario 2, lots of large random changes
                GC.Collect();
            }
            System.Console.WriteLine("Got to changelist " + x.ToString());
            System.Threading.Thread.Sleep(100);
        }
    }
}

Если я не справляюсь с памятью в любом сценарии, если существует что-то, что я должен определить с профилировщиком, сообщить мне. Это - довольно простая стандартная программа там.

К сожалению, похоже, что ответ @Kevin является правильным - это - ошибка в.NET и как.NET обрабатывает объекты, больше, чем 85k. Эта ситуация кажется мне чрезвычайно странный; Powerpoint мог быть переписан в.NET с этим видом ограничения или каким-либо из других приложений Пакета офисных программ? 85k, кажется, мне не много пространства, и я также думал бы, что любая программа, которая использует так называемые 'большие' выделения часто, становилась бы нестабильной в течение нескольких дней к неделям при использовании.NET.

Править: Похоже, что Kevin прав, это - ограничение GC.NET. Для тех, кто не хочет следовать за всем потоком.NET имеет четыре "кучи" GC: gen0, gen1, gen2, и LOH ("Куча" для больших объектов). Все это - 85k или меньший, идет на одну из первых трех "кучи", в зависимости от времени создания (перемещенный от gen0 до gen1 к gen2, и т.д.). Объекты, больше, чем 85k, помещаются в LOH. LOH никогда не уплотняется, поэтому в конечном счете, выделения типа, который я делаю, в конечном счете вызовут ошибку OOM, поскольку объекты рассеиваются о том пространстве памяти. Мы нашли, что перемещение в.NET 4.0 действительно помогает проблеме несколько, задерживая исключение, но не предотвращая его. Честно говоря, это чувствует немного как 640k барьер - 85k, должен быть достаточно для любого пользовательского приложения (для перефразирования этого видео обсуждения GC в.NET). Для записи Java не показывает это поведение со своим GC.

40
задан Community 23 May 2017 в 11:54
поделиться

8 ответов

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

http://connect.microsoft.com/VisualStudio/feedback/details/521147/large-object-heap-fragmentation-causes-outofmemoryexception

Опасности кучи больших объектов:
http: // www .simple-talk.com / dotnet / .net-framework / the-dangers-of-the-large-object-heap /

Вот ссылка на то, как собирать данные в куче больших объектов (LOH):
http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

В соответствии с этим, похоже, нет способа сжать LOH. Я не могу найти ничего более нового, в котором прямо говорится, как это сделать, и поэтому кажется, что это не изменилось во время выполнения 2.0:
http://blogs.msdn.com/maoni/archive/2006/04 /18/large-object-heap.aspx

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

22
ответ дан 27 November 2019 в 01:47
поделиться

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

Запустите perfmon и посмотрите на счетчики размера кучи .NET и байтов личного пользования. Если размер кучи остается довольно постоянным, но частные байты растут, значит, у вас есть проблема с собственным кодом, и вам нужно выломать инструменты C ++ для ее отладки.

Предполагая, что проблема в.NET, вам следует запустить профилировщик для кода, например профилировщик Ant от Redgate или DotTrace от JetBrain. Это скажет вам, какие объекты занимают место и не собираются быстро. Вы также можете использовать WinDbg с SOS для этого, но это неудобный интерфейс (хотя и мощный).

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

Что бы вы ни делали, «обильное разбрызгивание» вызовов GC.Collect почти всегда является неправильным способом решения проблемы.

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

22
ответ дан 27 November 2019 в 01:47
поделиться

Небольшое отступление: мусор .NET сборщик выполняет «быстрый» сборщик мусора, когда функция возвращается к вызывающему. Это приведет к удалению локальных переменных, объявленных в функции.

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

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

Избегайте соблазна явно вмешаться в GC.Collect .

2
ответ дан 27 November 2019 в 01:47
поделиться

Используйте Process Explorer (из Sysinternals), чтобы увидеть, что такое Large Object Heap для вашего приложения. Лучше всего будет сделать массивы меньше, но больше. Если вы сможете избежать выделения объектов в LOH, вы не будете получать OutOfMemoryExceptions и вам не придется вызывать GC.Collect вручную.

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

4
ответ дан 27 November 2019 в 01:47
поделиться

Вы проверяли наличие утечек памяти? Я использовал .NET Memory Profiler с небольшим успехом в проекте, в котором имелся ряд очень тонких и раздражающе постоянных (каламбурных) утечек памяти.

В качестве проверки работоспособности убедитесь, что вы вызываете Dispose для любых объектов, реализующих IDisposable .

1
ответ дан 27 November 2019 в 01:47
поделиться

Вы можете реализовать свой собственный класс массива, который разбивает память на несмежные блоки. Скажем, у вас есть массив 64 на 64 [64,64] ushort-массивов, которые выделяются и освобождаются отдельно. Затем просто выберите нужный. Местоположение 66,66 будет в ячейке [2,2] в массиве в [1,1].

Тогда вы сможете увернуться от кучи больших объектов.

1
ответ дан 27 November 2019 в 01:47
поделиться

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

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

Расширение...

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

1,5 ГБ (используемое пространство памяти приложения) / 32 МБ (большой размер запроса памяти) ~= 46

0
ответ дан 27 November 2019 в 01:47
поделиться

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

Конечно, это в некоторой степени зависит от того, что делает неуправляемый код. Вы конкретно не заявили, что он выделяет память, но у меня сложилось впечатление, что это так. Если да, то это именно то, для чего был разработан этот метод. Опять же, если неуправляемая библиотека выделяет много памяти, то также возможно, что она фрагментирует память, что полностью вне контроля GC даже с AddMemoryPressure . Надеюсь, это не так; если это так, вам, вероятно, придется реорганизовать библиотеку или изменить способ ее использования.

P.S. Не забудьте вызвать GC.RemoveMemoryPressure , когда вы, наконец, освободите неуправляемую память.

(PPS Некоторые другие ответы, вероятно, верны, гораздо более вероятно, что это просто утечка памяти в вашем коде; особенно если это обработка изображений, держу пари, что вы неправильно избавляетесь от своего экземпляров IDIsposable . Но на тот случай, если эти ответы никуда вас не приведут, вы можете выбрать другой путь.)

3
ответ дан 27 November 2019 в 01:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: