Under the Hood of .NET Memory Management / Под капотом у памяти в .Net: Chapter 3: A Little More Detail
| Английский оригинал | Перевод на русский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
||
|
К концу данной главы вы будете обладать фундаментальным пониманием устройства памяти в .Net. masterofsea147 30.12.19 в 23:29 |
||
|
В листинге 3.1 показан код, который может привести к подобной ситуации. Если метод DoABitMoreWork вызывается после того, как его экземпляр объекта был помещен во второе поколение, он будет содержать ссылку на экземпляр SpannerInWorks через переменную _createdLater, расположенный в нулевом поколении. masterofsea147 30.12.19 в 23:33 |
||
|
Эта ситуация является проблемой, из-за оптимизации сборки мусора, в результате которой GC собирает только те объекты, которые принадлежат соответствующему поколению сборки мусора и младше. masterofsea147 30.12.19 в 23:38 |
||
|
Проблема заключается в том, что без поиска всех возможных ссылок, GC может предположить, что объект больше не требуется, и подвергнуть его сборке. masterofsea147 30.12.19 в 23:40 |
||
|
.NET требуется возможность обнаружения изменений в экземплярах объектов в поздних поколениях, и хорошая новость в том, что у него есть способ сделать это. masterofsea147 30.12.19 в 23:47 |
||
|
Таблица карт masterofsea147 30.12.19 в 23:54 |
||
|
Когда объекты нулевого или первого поколения создаются внутри объектов второго, механизм исполнения помещает запись в таблицу карт. masterofsea147 30.12.19 в 23:58 |
||
|
Заманчиво предположить, что таблица карт состоит из двух столбцов - идентификатора ссылки на объект и битового флага и имеет одну запись на объект. Проблема такой реализации заключается в том, что огромное количество объектов, которым потребуется своя запись, значительно снизит производительность. masterofsea147 31.12.19 в 00:05 |
||
|
Вместо этого .NET хранит последовательность бит, каждый бит в которой отображает 128 байтов динамической памяти. Если объект, находящийся в диапазоне адресов этого бита, был создан объектом нулевого или первого поколения, то этот бит устанавливается. masterofsea147 31.12.19 в 00:13 |
||
|
После того как происходит сборка мусора нулевого или первого поколения, любые объекты, находящиеся в диапазонах адресов соответствующих установленным битам в таблице карт, включаются в проверку «в списке использования», как и все их дочерние ссылки. masterofsea147 31.12.19 в 00:17 |
||
|
Как показано на рисунке 3.1, объект B создает ссылку на объект E и включает бит таблицы карт, соответствующий диапазону адресов в котором он находится. В следующий раз, когда будет произведена сборка мусора нулевого поколения, будут проверены все дочерние ссылки для объектов, расположенных в диапазоне байтов 0–127, который помечен в таблице карт. masterofsea147 31.12.19 в 00:25 |
||
|
Немного о сегментах masterofsea147 31.12.19 в 00:27 |
||
|
Сегменты - это просто единицы выделения памяти; когда GC необходимо расширить кучу, он запрашивает дополнительный сегмент. Точно так же, когда куча сжимается или у нее обнаруживается избыточное свободное пространство, она может освободить сегменты обратно для ОС. masterofsea147 31.12.19 в 00:30 |
||
|
Размер сегмента является динамическим, настраивается во время исполнения и зависит от используемого режима GC. Например, типичный сегмент в режиме рабочей станции - 16 Мб. Мы рассмотрим различия между режимами работы GC позже. masterofsea147 31.12.19 в 00:31 |
||
|
В начале выполнения процесса для SOH выделяется один сегмент, называемый эфемерным. Когда он заполняется, происходит сборка мусора и добавление нового сегмента. Любые выживающие в нулевом поколении объекты копируются в новый сегмент (становясь объектами первого поколения). Рисунок 3.2 иллюстрирует данный процесс. masterofsea147 31.12.19 в 00:36 |
||
|
На рисунке 3.2 эфемерный сегмент содержит корневые объекты всех трех поколений. Чтобы диаграмма не стала слишком запутанной, я не включил в нее никаких корневых ссылок, поэтому давайте предположим, что все объекты укоренены. masterofsea147 31.12.19 в 00:37 |
||
|
Со временем эфемерный сегмент заполняется настолько, что происходит полная сборка мусора, и у ОС запрашивается новый сегмент, который становится новым эфемерным сегментом. Объекты I и H копируются в него (так как они укоренены), присоединяясь к первому поколению, а все остальное остается в старом сегменте. То есть, фактически, второе поколение - это всего лишь коллекция старых эфемерных сегментов. masterofsea147 31.12.19 в 00:44 |
||
|
Сегменты и страницы masterofsea147 31.12.19 в 00:46 |
||
|
Страницы могут находиться в одном из трех состояний: заблокированном, измененном и свободном masterofsea147 31.12.19 в 00:47 |
+1 | |
|
Свободные страницы доступны запрашивающему память потоку. masterofsea147 05.01.20 в 20:18 |
||
|
Производительность сборки мусора masterofsea147 31.12.19 в 00:51 |
||
|
Рассмотрим, например, сжатие кучи. Объекты перемещаются в памяти, а чтобы обеспечить целостность кучи, сборщик мусора должен приостановить выполнение потоков исполняемого процесса. Очевидно, что это не идеально для производительности, но необходимо для обеспечения эффективного управления кучей. masterofsea147 05.01.20 в 20:24 |
||
|
Поиск компромисса между производительностью и эффективностью кучи стал причиной разработки нескольких режимов работы GC. Как было упомянуто ранее, GC имеет два режима: режим рабочей станции, который настроен для обеспечения максимальной отзывчивости пользовательского интерфейса, и режим сервера, который настроен для обеспечения максимальной пропускной способности. Поведение GC в этих режимах значительно различается, поэтому я опишу их подробнее. masterofsea147 05.01.20 в 20:29 |
||
|
Режим рабочей станции masterofsea147 05.01.20 в 20:50 |
||
|
Начиная с .NET Common Language Runtime (CLR) версии 1.0, сборщик мусора в режиме рабочей станции может работать как параллельно, так и не параллельно; это зависит лишь от того в каком потоке выполняется GC. В не параллельном режиме выполнение потока кода приложения приостанавливается, и GC затем запускается в потоке приложения. Он был разработан для однопроцессорных машин, где одновременное выполнение потоков не представляется возможным. masterofsea147 05.01.20 в 20:56 |
||
|
Поскольку многоядерные / многопроцессорные настольные компьютеры в настоящее время очень распространены, параллельная работа GC теперь является нормой и значением по умолчанию. masterofsea147 05.01.20 в 20:57 |
||
|
Параллельный режим рабочей станции masterofsea147 05.01.20 в 21:04 |
||
|
Также стоит помнить, что параллельный сборщик мусора срабатывает только при полной сборке мусора, поэтому при сборке нулевого и первого поколений все еще происходит приостановка потоков. Однако вместо того, чтобы просто приостановить все потоки на время сборки мусора, GC стремится приостанавливать потоки на короткие периоды, обычно дважды за время выполнения. В отличие от однопоточного GC, который приостанавливает все потоки на время всего процесса сборки мусора, параллельный GC не вызывает такой задержки. masterofsea147 05.01.20 в 21:11 |
||
|
Вот как это работает. Когда происходит полная сборка мусора, определяются начальная и конечная позиции выделенных объектов в куче, и сборка мусора ограничивается этим «доменом сборщика мусора». Суть в том, что приложение может продолжать размещать объекты в куче за пределами этого домена. masterofsea147 05.01.20 в 21:14 |
||
|
Рисунок 3.3 иллюстрирует процесс, при котором домен GC инициализируется начальной и конечной позициями, а затем запускается GC (имейте в виду, что он не показывает результаты сборки). Мы также видим, что во время работы GC приложение выделяет новые объекты в куче (L, M, N и O). masterofsea147 05.01.20 в 21:19 |
||
|
Приложение может продолжать выделять объекты до тех пор, пока не будет достигнут предел эфемерного сегмента, то есть размер сегмента за вычетом места, которое мы будем называть «Зоной без движения». Как только этот предел достигнут, выполнение приложения приостанавливается до полного завершения GC. masterofsea147 05.01.20 в 21:25 |
||
|
На рисунке 3.3 объект M стал без корня, но он находится за пределами текущего домена GC, поэтому его пространство не может быть собрано. Теперь он способствует использованию эфемерного сегмента, даже если он больше не используется, и это повысит вероятность приостановки потока. Это раздражает! masterofsea147 05.01.20 в 21:25 |
+1 | |
|
В идеале нам нужен способ для сборки мусора как внутри текущего домена GC, так и внутри памяти вновь выделенных объектов, а также для возможности расширения кучи в случае необходимости, чтобы можно было полностью избежать приостановки потоков. Угадайте, что сделали эти хитрые разработчики Microsoft ... masterofsea147 05.01.20 в 21:37 |
||
|
Фоновый режим рабочей станции GC (.NET 4.0) masterofsea147 05.01.20 в 21:34 |
||
|
При использовании фонового GC для вновь выделенных объектов может быть запущена сборка мусора нулевого или первого поколения, пока выполняется полная сборка мусора второго поколения. masterofsea147 05.01.20 в 21:40 |
||
