Как я могу получить сборку .NET для сбора мусора агрессивно?

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

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

РЕДАКТИРОВАТЬ: Вот какой-то очень специфический код, который приведет к сбою. Согласно this SO question, мне не нужно удалять объекты BitmapSource здесь. Вот наивная версия, в ней нет GC.Collects. Обычно он прерывается на итерации от 4 до 10 процедуры отмены. Этот код заменяет конструктор в пустом проекте WPF, так как я использую WPF. Я делаю wackiness с bitmapsource из-за ограничений, которые я объяснил в своем ответе на @dthorpe ниже, а также в требованиях, перечисленных в этом вопросе SO.

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<ushort[]> theList = new List<ushort[]>();//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<ushort[]> theList = new List<ushort[]>();//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 обрабатывает объекты размером более 85 КБ. Эта ситуация кажется мне чрезвычайно странной; может ли Powerpoint быть перезаписана в .NET с таким ограничением или любым другим приложением Office? 85k не кажется мне большим пространством, и я также думаю, что любая программа, использующая так называемые "большие" распределения, часто становилась нестабильной в течение нескольких дней и недель при использовании .NET.

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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

Что бы вы ни делали, "щедро разбрызгивая" вызовы GC.Collect - это почти всегда неправильный способ попытаться решить проблему.

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

Ответ 3

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

Помимо обработки распределений более удобным для GC способом (например, повторное использование массивов и т.д.), теперь есть новая опция: вы можете вручную вызвать уплотнение LOH.

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;

Это приведет к уплотнению LOH при следующем сборе коллекции gen-2 (либо самостоятельно, либо явным вызовом GC.Collect).

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

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

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

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

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

Ответ 9

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

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

Extended...

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

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

Ответ 10

Лучший способ сделать это - это как показать эту статью, она находится на испанском языке, но вы точно понимаете код. http://www.nerdcoder.com/c-net-forzar-liberacion-de-memoria-de-nuestras-aplicaciones/

Здесь код в случае ссылки get brock

using System.Runtime.InteropServices; 
....
public class anyname
{ 
....

[DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]

private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize);

public static void alzheimer()
{
GC.Collect();
GC.WaitForPendingFinalizers();
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
} 

....

вы вызываете alzheimer() для очистки/освобождения памяти.

Ответ 11

вы можете использовать этот метод:

public static void FlushMemory()
{
    Process prs = Process.GetCurrentProcess();
    prs.MinWorkingSet = (IntPtr)(300000);
}

три способа использования этого метода.

1 - после удаления управляемого объекта, такого как класс,....

2 - создайте таймер с такими интервалами 2000.

3 - создайте поток, чтобы вызвать этот метод.

Я предлагаю вам использовать этот метод в потоке или таймере.

Ответ 12

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

Вы попадаете в ситуацию, когда GC не думает, что вам не хватает памяти, потому что большинство вещей в вашей куче 1-го поколения - 8 байтовых ссылок, где на самом деле они похожи на айсберги в море. Большая часть памяти ниже!

Вы можете использовать эти вызовы GC:

  • System:: GC:: AddMemoryPressure (sizeOfField);
  • System:: GC:: RemoveMemoryPressure (sizeOfField);

Эти методы позволяют GC видеть неуправляемую память (если вы предоставите ей правильные цифры).