Сборщик мусора:
Паралельный - может собирать мусор из нескольких потоков.
Конкурентный - не прерывает работу основного приложения.
Компактный - поддерживает пространственную локальность программы, если программа обладает этим свойством.
Программа обладает пространственной локальностью, если данные к которым было обращение недавно находятся рядом с данными, которые будут использованы сейчас.
Паралельность уменьшает время сборки мусора за счёт использования нескольких ядер.
Конкурентность даёт возможность приложению выпольнять работу во время сборки мусора.
Компактность поволяет оптимизировать время обращения к данным за счёт размещения данных, которые будут использованы "совместно", в кеше ядра или процессора, так же при определённой архитектуре(сейчас сборщики поддерживают оптимизацию для NUMA архитектуры(например поддерживается большинством топовых процессоров с архитектурой x86_64)) расположение таких данных в оперативной памяти ближе к используемому их ядру уменьшит время обращения к этим данным.
Сборщик мусора надо оценивать по следующим критериям:
стоимость
стабильность работы
время паузы приложения
работа с большими объёмами данных
удобство настройки
эффективное использование современной архитектуры
компактификация данных программы
пропускная способность
потребление ресурсов
Эти характеристики зависят одна от другой и улучшение одних характеристих может повлечь ухудшение других.
Распространённые сборщики мусора:
Serial gc - самое низкое потребление ресурсов. Эффективен для использования в embedded системах.
Parallel gc - обладает самой высокой пропускной способностью, эффективен для приложений где длина паузы неважна, а важна пропускная способность(пример батч обработка больших объёмов данных)
ZGC - низкое latency 1 миллисекунда
Shenandoah - низкое latency 1-10 миллисекунд
C4 - без пауз платный
Давайте более подробно рассмотрим бесплатные сборщики, которые используются для low latency приложений: ZGC, Shenandoah
Плюсы zgc, shenandoah: конкурентность и маленькая пауза на приостановку приложения, компактность, параллельность.
ZGC, Shenandoah приостанавливают работу приложения на незначительное время
Заявленные характеристики для ZGC до 1 миллисекунды в stop the world(STW)
Shenandoah от 1 до 10 миллисекунд
Достигается это за счёт конкуретной разметки достижимых объектов, с последующим конкурентным копированием в свободные регионы(Shenandoah), либо уплотнением используемых областей(ZGC).
В G1 была только конкурентная разметка.
Уплотнение позволяет в большинстве случаем уменьшить вероятность приостановки приложения(на Full GC) из-за недостаточного количества свободной памяти или аварийной остановки приложения с OutOfMemoryError.
Конкурентная разметка и сбор накладывают дополнительные расходы на выполнение кода приложения.
При присваивании или чтении ссылок из кучи(а = b.c, не a = b) происходит дополнительная проверка с дальнейшим выставлением барьера.
Большая часть времени в STW тратится на сбор корневых объектов. Собираются корневые объекты из java стеков, статических данных, берутся JNI ссылки, так же сборка происходит из других мест.
В ZGC в jdk16 сбор данных со стека происходит в конкурентном режиме.
И то и другое приложение разбивает кучу на блоки, что позволяет собирать только часть кучи и упрощает паралельное копирование и уплотнение. И там и там отсутствуют поколения.
Аглоритм Шенандоа:
1) Пауза
Собираем корневые объекты(со стэков, из статики, jni, whatever) и помечаем, как доступные.
2) Конкурентно размечаем граф объектов
Те необработанные объекты, на которые меняется ссылка с необработанного объекта на доступную ссылку(ссылка с доступного объекта, либо из корня), добавляются в очередь. Читаем эту очередь и помечаем эти объекты, как доступные и обходим их потомков.
Для того, чтобы отследить эти изменения ссылок используется барьер.
Прекращаем маркировку, когда обошли весть граф и очередь уменьшилась до определённого размера, возможно до нулевого.
3) Пауза
Обрабатываем оставшиеся объекты в очереди.
Выгружаем неиспользуемые классы.
Выбираем те регионы, в которых больше всего мусора.
4) Конкурентно копируем объекты из выбранных регионов
Сборщик копируем объекты в новые блоки, при этом добавляя ссылку на новый объект, в заголовок старой копии.
Теперь мы можем читать объекты переходя по ссылке из старой копии.
Для сохранения консистентности, приложение может делать изменения только в новой копии. Чтобы не ждать копирования сборщиком - приложение само копирует объект, если это ещё не сделано.
С помощью кас и барьера добиваемся того, чтобы в итоге в новом блоке осталась только одна копия.
5) Пауза: Cобираем ещё раз корневые объекты.
6) После этого конкуретно обходим граф и меняем ссылки во всех объектах со старых на новые.
Освобождаем все блоки со старыми объектами.
Конец.
Полезно прочитать вторую половину седьмой главы Ахо Ульмана.
Полезные ссылки:
https://docs.oracle.com/en/java/javase/16/docs/specs/man/java.htmlhttps://wiki.openjdk.java.net/display/zgc/Mainhttps://wiki.openjdk.java.net/display/shenandoah/Main https://malloc.se/blog/zgc-jdk16https://www.researchgate.net/publication/306112816_Shenandoah_An_open-source_concurrent_compacting_garbage_collector_for_OpenJDKhttps://go.azul.com/continuously-concurrent-compacting-collector