Как работает бар для настольных компьютеров и писателей?

Я читаю некоторые материалы о сборке мусора на Java, чтобы узнать больше, что действительно происходит в процессе GC.

Я столкнулся с механизмом, называемым "карточным столом". Я искал его и не нашел исчерпывающей информации. Большинство объяснений довольно мелкие и описывают его как волшебство.

Мой вопрос: как работает карточный стол и барьер записи? Что помечено в карточных таблицах? Как тогда сборщик мусора знает, что на конкретный объект ссылается другой объект, который сохраняется в старшем поколении.

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

Ответы

Ответ 1

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

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

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

Кроме того, вместо сохранения явного списка ведер, вы резервируете некоторое пространство для каждого возможного ведра вверх. В частности, существует массив из N бит или байтов, где N - количество ведер, так что значение i th равно 0, если в ведре i th нет молодых указателей или 1, если он содержит молодые указатели, Это собственно карточный стол. Обычно это пространство выделяется и освобождается вместе с большим блоком памяти, используемым в качестве (части) кучи. Он даже может быть встроен в начало блока памяти, если ему не нужно расти. Если все адресное пространство используется как куча (что очень редко), приведенная выше формула дает числа, начинающиеся с start_of_memory_region >> K вместо 0, поэтому, чтобы получить индекс в карточном столе, вы должны вычесть начало начального адреса куча.

Таким образом, когда барьер записи обнаруживает, что оператор some_obj.field = other_obj; хранит молодой указатель в старом объекте, он делает это:

card_table[(&old_obj - start_of_heap) >> K] = 1;

Где &old_obj - адрес объекта, у которого теперь есть молодой указатель (который уже находится в регистре, потому что он просто решил ссылаться на старый объект). Во время незначительного GC сборщик мусора смотрит на карточный стол, чтобы определить, какие области кучи сканируются для молодых указателей:

for i from 0 to (heap_size >> K):
    if card_table[i]:
        scan heap[i << K .. (i + 1) << K] for young pointers

Ответ 2

Когда-то я написал статью, объясняющую механику молодой коллекции в JVM HotSpot. Общие сведения о GC-паузах в JVM, HotSpot minor GC

Принцип грязного карточного барьера для записи очень прост. Каждый раз, когда программа изменяет ссылку в памяти, она должна маркировать страницу измененной памяти как загрязненную. В JVM имеется специальная таблица карт, и каждая 512-байтная страница памяти связывает одну байтовую запись в таблице карт.

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