Skip to content

Latest commit

 

History

History
35 lines (18 loc) · 8.21 KB

03-04-MemoryManagement-GC-Sweep-Collect.md

File metadata and controls

35 lines (18 loc) · 8.21 KB

Sweep & Collect

{.big-quote} Адаптация курса в процессе

Мы рассмотрели практически все стадии, кроме последней. Эта фаза зачистки и сжатия.

Если на фазе планирования было решено использовать sweep, мы должны осуществить GC путем отдачи всех неиспользуемых участков на переиспользование. Когда наши allocation context, которые существуют от разных потоков, начнут заканчиваться, они будут занимать эти свободные участки между другими объектами, таким образом исключая фазу сжатия. Фаза сжатия работает дольше. После этого этапа мы имеем такую таблицу 01:27. У нас есть поколения с бакетами, которые указывают на односвязаный список свободных участков. Бакеты сгруппированы по размерам. Внутри одного бакета идет список участков примерно одного размера. Мы можем попросить у бакетов участок нужного размера, и он по принципу first fit найдет нужный бакет и через best fit найдет подходящий участок внутри бакета.

Gaps, которые меньше размера объекта игнорируются.

После этого saved_pre_plug и saved_post_plugs расставляются по местам. Потом выполняется работа по обновлению очереди на финализацию. И затем - перестроение сегментов памяти.

Например, может так случится, что после GC у нас часть страниц или сегментов больше не нужны. Зачем их держать, если можно отдать кому-то еще? Операционная система большая. Если не отдать, то все соседние приложения будут ругаться, что работают медленно. Так и есть: потому что наше приложение не освободило оперативную память под соседей.

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

Для меня было откровением, что в обоих хипах механизмы одни и те же. Просто в одном хипе у них разные приоритеты. В LOH compacting в настолько низком приоритете, что часть алгоритма просто отключена. Но в целом SOH и LOH использует одни и те же алгоритмы.

Compacting.

Если необходимо создается новый эфемерный сегмент. Если фаза планирования решила, что после сжатия места под gen_0 будет мало - создаем новый сегмент. Заменяем все ссылки на корректные. Для этого у нас есть информация по gaps&plugs, по которым легко посчитать все смещения. Перед тем, как сжимать, мы должны все ссылки поменять. Собирая gen_0 мы просматриваем его и карточный стол старшего поколения. Когда это сделано - начинаем менять ссылки в фазе сжатия до самого сжатия, чтобы не потерять информация по gaps и офсетам. Сканируем все места, производя замену. Сначала ссылки со стека, которые включают в себя все локальные переменные и параметры методов, переданные через стек, а так же eager roots collection. Потом ссылки с полей объектов, полученные через карточный стол. Затем ссылки с полей объектов SOH и LOH с этапа обхода графа. Также ссылки с pre и post plugs, потому что при сохранении туда могли попасть и ссылки. Pinned plugs queue. Ссылки с полей объектов, находящихся в finalization queue. Ссылки с handle tables.

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

Далее выполняется копирование объектов на их новые места. После того, как мы поменяли все адреса, происходит сама фаза сжатия. Из последних байт gaps берется значение офсета, и на него смещаются все выжившие объекты, чтобы сжать кучу. Естественно, кроме запиненых объектов и тех, которые размещены после них.

После этого восстанавливаются все pre и post plugs участки и исправляем положение поколений (после GC нулевое поколение становится первым, первое - вторым и нулевое сдвигается так, чтобы можно было выделять память дальше).

После этого удаляется и разкоммичеивается память из-под полностью освободившихся сегментов.

Перед каждым запиненым объектом образуется свободный участок. Его надо сохранить для дальнейшего выделения памяти. То есть объект запинен. Слева от него занятое место. И между занятым местом и запиненым объектом - свободные участки. Чтобы их потом переиспользовать, они сохраняются в список свободных участков. Чтобы это сделать нужно убедиться, что есть нужный бакет, либо создать новый, и уже оттуда спустить ссылку на односвязаный список занятых участков определенного размера.

Получается, что задача sweep - создать односвязаный список на свободных участках. Сжатие - это дольше. Чтобы сработал sweep, надо группировать объекты, срок жизни которых совпадает. Это максимум, что мы можем сделать.

Чтобы не мешать GC, мы не используем GCHandle bind. По возможности, мы используем только fixed. В этом случае мы можем избежать пининга, потому что fixed - это просто напоминание GC, что если он здесь очутился, то пинить надо.