-1

Я пытался понять работу Process Memory. Так что я попытался следующий кусок кодаРабота с памятью процесса/сборщиком мусора

public void OpenFormWithoutList() 
    { 
     Form2 form = null; 
     int index = 0; 
     while (index < 5000) 
     { 
      form = new Form2(); 
      form.ShowDialog(); 
      index++; 
     } 
    } 
    public void OpenFormWithList() 
    { 
     Form2 form = null; 
     List<Form> list = new List<Form>(); 
     int index = 0; 
     while (index < 5000) 
     { 
      form = new Form2(); 
      list.Add(form); 
      form.ShowDialog(); 
      index++; 
     } 
     list = null; 
    } 

И в Form2.cs я закрываю форму в OnLoad событие, так что контроль должен вернуться к исходной форме (Form1) снова.

При запуске обоих методов seprately от начала, следующее наблюдение после метода выполняет:

Старт: 20 МБ OpenFormWithList(): 29MB

Старт: 20Мб OpenFormWithoutList(): 25MB

Когда вызывается OpenFormWithoutList(), GC собирает формы, поэтому использование памяти не достигает 29 МБ. Но как только этот метод заканчивается, слишком часто использование памяти не возвращается к начальной фазе, то есть 20 МБ.

Так почему же память не очищается и что именно потребляет память?

+1

Боковое примечание: 'list = null;' в лучшем случае избыточно. GC и JIT сотрудничают, чтобы понять время жизни переменных. Они уже знают, в конце метода, что переменная 'list' больше не является« живой »ссылкой, и эта переменная не может использоваться для сохранения объекта ссылки. –

+0

Имейте в виду, что существует ограниченное количество ручек ресурсов, необходимых каждой форме. Это может осложнить ваши тесты. Вероятно, лучше избегать использования любой формы пользовательского интерфейса для тестов памяти. – Enigmativity

ответ

2

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

GC.Collect(); 
GC.WaitForPendingFinalizers(); 
+0

Вы хотите другой 'GC.Collect' после вызова' WaitForPendingFinalizers', иначе память, используемая экземплярами, которые были завершены, не будет исправлена. – Luaan

+0

@ Luaan: Если есть одноразовые объекты с завершением и не были удалены, вам нужно еще раз вызвать GC.Collect(). – CharithJ

1

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

Наиболее важной частью здесь является то, что при каждом распределении время выполнения проверяет, сколько памяти было выделено с момента последней попытки сбора мусора, и если это проходит определенный порог, начинается сбор. Таким образом, он будет проходить через всю память (для коллекции поколения 2 - сбор низшего поколения собирается только через кучи более низкого поколения), обратите внимание на все объекты, которые не имеют к ним ссылок, и очищает их. Наконец, это будет compact куча - переместите все объекты вокруг, чтобы сделать смежное пространство в куче. Это очень важно, так как .NET не выделяет середину кучи [1] - это что-то вроде суффиксного стека, который позволяет «выскакивать» из середины.

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

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

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

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

[1] Обратите внимание, что это относится только к основным кучам. Большая куча объектов действительно позволяет выделять посередине, и она не компактна. Начиная с .NET 4.5, есть возможность вручную принудительно создать коллекцию кучи на LOH.

Смежные вопросы