Сборка мусора на рабочей станции производится в том же потоке, где она была инициирована, и сохраняет тот же самый приоритет, который был назначен во время запуска. Это может привести к состязанию с другими потоками в приложении.
Сборка мусора на сервере осуществляется в нескольких выделенных потоках, которым назначен уровень приоритета THREAD_PRIORITY_HIGHEST (тема многопоточности раскрывается в главе 15). Для выполнения сборки мусора каждый ЦП получает выделенную кучу и отдельный поток. В итоге сборка мусора на сервере может стать крайне ресурсоемкой.
Фоновая сборка мусора
Начиная с версии .NET 4.0 и продолжая в .NET Core, сборщик мусора способен решать вопрос с приостановкой потоков при очистке объектов в управляемой куче, используя фоновую сборку мусора. Несмотря на название приема, это вовсе не означает, что вся сборка мусора теперь происходит в дополнительных фоновых потоках выполнения. На самом деле, если фоновая сборка мусора производится для объектов, принадлежащих к неэфемерному поколению, то исполняющая среда .NET Core может выполнять сборку мусора в отношении объектов эфемерных поколений внутри отдельного фонового потока.
В качестве связанного замечания: механизм сборки мусора в .NET 4.0 и последующих версиях был усовершенствован с целью дальнейшего сокращения времени приостановки заданного потока, которая связана со сборкой мусора. Конечным результатом таких изменений стало то, что процесс очистки неиспользуемых объектов поколения 0 или поколения 1 был оптимизирован и позволяет обеспечить более высокую производительность приложений (что действительно важно для систем реального времени, которые требуют небольших и предсказуемых перерывов на сборку мусора).
Тем не менее, важно понимать, что ввод новой модели сборки мусора совершенно не повлиял на способ построения приложений .NET Core. С практической точки зрения вы можете просто разрешить сборщику мусора выполнять свою работу без непосредственного вмешательства с вашей стороны (и радоваться тому, что разработчики в Microsoft продолжают улучшать процесс сборки мусора в прозрачной манере).
Тип System.GC
В сборке mscorlib.dll предоставляется класс по имени System.GC, который позволяет программно взаимодействовать со сборщиком мусора, применяя набор статических членов. Имейте в виду, что необходимость в прямом взаимодействии с классом System.GC внутри разрабатываемого кода возникает редко (если вообще возникает). Обычно единственной ситуацией, когда будут использоваться члены System.GC, является создание классов, которые внутренне работают с неуправляемыми ресурсами. Например, может строиться класс, в котором присутствуют вызовы API-интерфейса Windows, основанного на С, с применением протокола обращения к платформе .NET Core, или какая-то низкоуровневая и сложная логика взаимодействия с СОМ. В табл. 9.2 приведено краткое описание некоторых наиболее интересных членов класса System.GC (полные сведения можно найти в документации по .NET Core).
Чтобы проиллюстрировать использование типа System.GC для получения разнообразных деталей, связанных со сборкой мусора, обновите операторы верхнего уровня в проекте SimpleGC:
using System;
Console.WriteLine("***** Fun with System.GC *****");
(window.adrunTag = window.adrunTag || []).push({v: 1, el: 'adrun-4-390', c: 4, b: 390})
// Вывести оценочное количество байтов, выделенных в куче.
Console.WriteLine("Estimated bytes on heap: {0}",
GC.GetTotalMemory(false));
// Значения MaxGeneration начинаются c 0, поэтому при выводе добавить 1.
Console.WriteLine("This OS has {0} object generations.n",
(GC.MaxGeneration + 1));
Car refToMyCar = new Car("Zippy", 100);
Console.WriteLine(refToMyCar.ToString());
// Вывести поколение объекта refToMyCar.
Console.WriteLine("Generation of refToMyCar is: {0}",
GC.GetGeneration(refToMyCar));
Console.ReadLine();
Вы должны получить примерно такой вывод:
***** Fun with System.GC *****
Estimated bytes on heap: 75760
This OS has 3 object generations.
Zippy is going 100 MPH
Generation of refToMyCar is: 0
Методы из табл. 9.2 более подробно обсуждаются в следующем разделе.
Принудительный запуск сборщика мусора
Не забывайте о том, что основное предназначение сборщика мусора связано с управлением памятью вместо программистов. Однако в ряде редких обстоятельств сборщик мусора полезно запускать принудительно, используя метод GC.Collect(). Взаимодействие с процессом сборки мусора требуется в двух ситуациях:
• приложение входит в блок кода, который не должен быть прерван вероятной сборкой мусора;
• приложение только что закончило размещение исключительно большого количества объектов, и вы хотите насколько возможно скоро освободить крупный объем выделенной памяти.
Если вы посчитаете, что принудительная проверка сборщиком мусора наличия недостижимых объектов может принести пользу, тогда можете явно инициировать процесс сборки мусора:
...
// Принудительно запустить сборку мусора
// и ожидать финализации каждого объекта.
GC.Collect();
GC.WaitForPendingFinalizers();
...
При запуске сборки мусора вручную всегда должен вызываться метод GC.WaitForPendingFinalizers(). Благодаря такому подходу можно иметь уверенность в том, что все финализируемые объекты (описанные в следующем разделе) получат шанс выполнить любую необходимую очистку перед продолжением работы программы. "За кулисами" метод GC.WaitForPendingFinalizers() приостановит вызывающий поток на время прохождения сборки мусора. Это очень хорошо, т.к. гарантирует невозможность обращения в коде к методам объекта, который в текущий момент уничтожается.
Методу GC.Collect() можно также предоставить числовое значение, идентифицирующее самое старое поколение, для которого будет выполняться сборка мусора. Например, чтобы проинструктировать исполняющую среду о необходимости исследования только объектов поколения 0, можно написать такой код: