Подтвердить что ты не робот

Сбор мусора на С# не выполнен. Зачем?

Я попробовал простой эксперимент, чтобы проверить функциональность сборщика мусора. Ссылка на 3.9 Автоматическое управление памятью (MSDN) об автоматическом управлении памятью в .NET. Для меня это звучало как эквивалент общего указателя на С++. Если контрольный счетчик объекта становится нулевым, он будет освобожден сборщиком мусора.

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

    public void experiment()
    {
        int[] a = new int[100000];
        int[] b = new int[100000];
        int[] c = new int[100000];
        int[] d = new int[100000];

        a = null;
        b = null;
        c = null;
        d = null;
    }

И вот результаты:

До выделения памяти

http://i.stack.imgur.com/ZhUKc.png

После выделения памяти

http ://i.stack.imgur.com/NyXJ6.png

Перед тем, как покинуть область действия

http ://i.stack.imgur.com/MnTrm.png

После выхода из области действия

http ://i.stack.imgur.com/MiA6T.png

Почему сборщик мусора не освободил память, выделенную массивами a, b, c, d, даже после того, как был установлен на нуль?

4b9b3361

Ответ 1

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

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

Все это означает, что время, затрачиваемое на сборщик мусора, не предсказуемо.

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

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

Ответ 2

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

Если вы действительно хотите заявить и освободить память явно, вам придется вызвать VirtualAlloc() через безопасный код Pinvoke.

Ответ 3

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

Также сборщик мусора может быть вызван явно с использованием метода GC.Collect(), но не рекомендуется явно использовать его.

Ответ 4

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

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

Итак, даже если сборка мусора произошла во время теста, вы не увидите ее в диспетчере задач или в Process Explorer, потому что CLR все равно не освободит его.

То, что вы описываете, называется ссылкой на сборщик мусора. Однако все существующие в настоящее время реализации CLI VES используют GC трассировки. Трассировочные GC не учитывают ссылки; они отслеживают их, только когда они бегут. Трассировочный GC не заметит, достигнут ли объект или нет, пока он фактически не проследит граф объекта, и он будет трассировать только граф объекта, когда ему нужно запустить коллекцию, т.е. Когда у вас закончилась нехватка памяти.

Ответ 5

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

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

... в какое-то неопределенное позднее время...

GC.Collect()

Одна важная вещь, по крайней мере для старой (неконкурентной) версии сборщика мусора, заключается в том, что сборщик мусора работает в другом потоке. Вы можете проверить, что в отладчике:

0:003> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
                                      PreEmptive   GC Alloc           Lock
       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
   0    1 1b08 0058f218      a020 Enabled  025553ac:02555fe8 0058b868     1 MTA
   2    2 1e9c 005a78c8      b220 Enabled  00000000:00000000 0058b868     0 MTA (Finalizer)

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

Но почему это важно?

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

GC.Collect();
Thread.Sleep(0);

Если вы беспокоитесь об управлении памятью, обязательно ознакомьтесь с удивительным ответом на IDisposable.

Свободная память

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

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

.NET получает память из Windows (ядро). Предположим, что он получает новую часть памяти из Windows, в которой нет объектов .NET внутри. С точки зрения Windows, память ушла (данная .NET). Однако, с точки зрения .NET, он бесплатный и может использоваться объектами.

Опять же, вы можете заметить, что в отладчике:

0:003> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                     60          71cb9000 (   1.778 Gb)           88.91%
<unknown>                                84           986f000 ( 152.434 Mb)  67.09%    7.44%
Image                                   189           2970000 (  41.438 Mb)  18.24%    2.02%
...

То, что сообщается как <unknown>, является виртуальной памятью с точки зрения Windows. В этом случае используется 150 МБ.

0:003>!dumpheap -stat
...
00672208       32      8572000      Free
...

Итак, вы можете видеть, что 8.5 MB свободны от .NET-точки зрения, но не были возвращены Windows (пока) и будут по-прежнему сообщаться как используемые там.

Измерительный рабочий набор

Если вы не изменили настройки столбца по умолчанию диспетчера задач, это еще хуже, потому что он покажет Рабочий набор, который является памятью только в ОЗУ. Однако часть памяти, возможно, была заменена на диск, поэтому она может не сообщаться диспетчером задач.