Как работает сборка мусора?
Очередной вопрос, ответ на который нужно начинать с уточнения: в каком именно сборщике мусора? Понятие сборщика мусора вводится в спецификации JVM, но внутренности зависят от реализации. Одна JVM может содержать несколько сборщиков, один сборщик может применять разные алгоритмы в разных случаях. Вообще говоря, в теории GC может делать ничего. Метод System.gc() обещает, что сборщик сделает «лучшую попытку» освободить память, то есть по факту не дает никаких гарантий.
GC (garbage collector) – центральная тема шуток про «джава тормозит». Это необходимая плата за стабильное автоматическое управление памятью. Поэтому это одна из самых бурлящих и меняющихся областей мира Java.
Основные подходы к сборке мусора – подсчет ссылок (reference counting) и обход графа достижимых объектов (mark-and-sweep, copying collection). Первый подход испытывает трудности с циклическими ссылками, в Java в основном используется второй.
Большинство сборщиков опирается на слабую гипотезу о поколениях. Гипотеза предполагает, что молодые объекты умирают чаще. Для этого куча делится на регионы по времени жизни объектов – поколения. Сборка мусора в них выполняется раздельно.
Общий для большинства сборщиков алгоритм описан во множестве статей, например, в этой. Суть его в том, что достижимые объекты помечаются и группируются, а недостижимые удаляются.
GC Roots – то, с чего начинается обход графа объектов на вопрос достижимости. Множество корневых объектов (root set) считается достижимым безусловно. Часто на интервью просят их перечислить.
Важное понятие для сборщиков мусора – Stop The World пауза. Это полная остановка потоков программы для безопасной сборки мусора и других системных операций. Происходит в специальных местах программы, которые называются safepoint.
Конкретный сборщик в HotSpot указывается в параметре запуска JVM. Каждый сборщик имеет много специфичных для него настроек. В Java 10 HotSpot доступно 4 сборщика:
Serial – однопоточный, с поколениями. Дает большой throughput (маленькая сумма задержек);
Parallel – многопоточный вариант Serial;
CMS (Concurrent Mark-Sweep) – дает меньшую latency (маленькие отдельные паузы), выполняя часть сборки вне Stop The World. Плата за это – меньший throughput. Способ сборки примерно как в предыдущих, работает с поколениями. В Java 9 уже объявлен deprecated;
G1 (Garbage First) – тоже направлен на уменьшение latency. Вместо поколений оперирует регионами;
Скоро будет добавлен новый сборщик Shenandoah;
Настоятельно рекомендуется к изучению очередной доклад Шипилёва (с продолжением) и цикл статей на хабре.
Сборщик мусора Garbage Collection
Чтобы понять, как работает сборщик мусора Garbage Collection, необходимо иметь представление о распределении памяти в JVM (Java Virtual Machine). Данная статья не претендует на то, чтобы покрыть весь объем знаний о распределении памяти в JVM и описании Garbage Collection, поскольку он слишком огромен. Да, к тому же, об этом достаточно информации уже имеется в Сети, чтобы желающие могли докапаться до ее глубин. Но, думаю, данной статьи будет достаточно, чтобы иметь представление о том, как JVM работает с памятью java-приложения.
Респределение памяти в JVM
Для рассмотрения вопроса распределения памяти JVM будем использовать широко распространенную виртуальную машину для Windows от Oracle HotSpot JVM (раньше был от Sun). Другие виртуальные машины (из комплекта WebLogic или open source JVM из Linux) работают с памятью по похожей на HotSpot схеме. Возможности адресации памяти, предоставляемые архитектурой ОС, зависят от разрядности процессора, определяющего общий диапазон емкости памяти. Так, например, 32-х разрядный процессор обеспечивает диапазон адресации 2 32 , то есть 4 ГБ. Диапазон адресации для 64-разрядного процессора (2 64 ) составляет 16 экзабайт.
Разделение памяти JVM
Память процесса делится на Stack (стек) и Heap (куча) и включает 5 областей :
- Stack
- Permanent Generation — используемая JVM память для хранения метаинформации; классы, методы и т.п.
- Code Cache — используемая JVM память при включенной JIT-компиляции; в этой области памяти кешируется скомпилированный платформенно-зависимый код.
- Eden Space — в этой области выделяется память под все создаваемые программой объекты. Жизненный цикл большей части объектов, к которым относятся итераторы, объекты внутри методов и т.п., недолгий.
- Survivor Space — здесь хранятся перемещенные из Eden Space объекты после первой сборки мусора. Объекты, пережившие несколько сборок мусора, перемещаются в следующую сборку Tenured Generation.
- Tenured Generation хранит долгоживущие объекты. Когда данная область памяти заполняется, выполняется полная сборка мусора (full, major collection).
Permanent Generation
Область памяти Permanent Generation используется виртуальной машиной JVM для хранения необходимых для управления программой данных, в том числе метаданные о созданных объектах. При каждом создании объекта JVM будет сохранять некоторый набор данных об объекте в области Permanent Generation. Соответственно, чем больше создается в программе объектов, тем больше требуется «пространства» в Permanent Generation.
Размер Permanent Generation можно задать двумя параметрами виртуальной машины JVM :
- -XX:PermSize – минимальный размер выделяемой памяти для Permanent Generation;
- -XX:MaxPermSize – максимальный размер выделяемой памяти для Permanent Generation.
Для «больших» Java-приложений можно при запуске определить одинаковые значения данных параметров, чтобы Permanent Generation была создана с максимальным размером. Это может увеличить производительность, поскольку динамическое изменение размера Permanent Generation является «дорогостоящей» (трудоёмкой) операцией. Определение одинаковых значений этих параметров может избавить JVM от выполнения дополнительных операций, связанных с проверкой необходимости изменения размера Permanent Generation.
Область памяти Heap
Куча Heap является основным сегментом памяти, где хранятся создаваемые объекты. Heap делится на два подсегмента : Tenured (Old) Generation и New Generation. New Generation в свою очередь делится на Eden Space и Survivor.
При создании нового объекта, когда используется оператор ‘new’, например byte[] data = new byte[1024], этот объект создаётся в сегменте Eden Space. Кроме, собственно данных для массива байт, создается также ссылка (указатель) на эти данные. Если места в сегменте Eden Space уже нет, то JVM выполняет сборку мусора. При сборке мусора объекты, на которые имеются ссылки, не удаляются, а перемещаются из одной области в другую. Так, объекты со ссылками перемещаются из Eden Space в Survivor Space, а объекты без ссылок удаляются.
Если количество используемой Eden Space памяти превышает некоторый заданный объем, то Garbage Collection может выполнить быструю (minor collection) сборку мусора. По сравнению с полной сборкой мусора данный процесс занимает немного времени, и затрагивает только область Eden Space — устаревшие объекты без ссылок удаляются, а выжившие перемещаются в область Survivor Space.
Размер сегмента Heap можно определить двумя параметрами : Xms (минимум) и -Xmx (максимум).
В чем отличие между сегментами Stack и Heap?
- Heap (куча) используется всеми частями приложения, а Stack используется только одним потоком исполнения программы.
- Новый объект создается в Heap, а в памяти Stack’a размещается ссылка на него. В памяти стека также размещаются локальные переменные примитивных типов.
- Объекты в куче доступны из любого места программы, в то время, как стековая память не доступна для других потоков.
- Если память стека полностью занята, то Java Runtime вызывает исключение java.lang.StackOverflowError, а если память кучи заполнена, то вызывается исключение java.lang.OutOfMemoryError: Java Heap Space.
- Размер памяти стека, как правило, намного меньше памяти в куче. Из-за простоты распределения памяти (LIFO), стековая память работает намного быстрее кучи.
Garbage Collector
Сборщик мусора Garbage Collector выполняет всего две задачи, связанные с поиском мусора и его очисткой. Для обнаружения мусора существует два подхода :
- Reference counting – учет ссылок;
- Tracing – трассировка.
Reference counting
Суть подхода «Reference counting» связана с тем, что каждый объект имеет счетчик, который хранит информацию о количестве указывающих на него ссылок. При уничтожении ссылки счетчик уменьшается. При нулевом значении счетчика объект можно считать мусором.
Главным недостатком данного подхода является сложность обеспечения точности счетчика и «невозможность» выявлять циклические зависимости. Так, например, два объекта могут ссылаться друг на друга, но ни на один из них нет внешней ссылки. Это сопровождается утечками памяти. В этой связи данный подход не получил распространения.
Tracing
Главная идея «Tracing» связана с тем, что до «живого» объекта можно добраться из корневых точек (GC Root). Всё, что доступно из «живого» объекта, также является «живым». Если представить все объекты и ссылки между ними как дерево, то необходимо пройти от корневых узлов GC Roots по всем узлам. При этом узлы, до которых нельзя добраться, являются мусором.

Данный подход, обеспечивающий выявление циклических ссылок, используется в виртуальной машине HotSpot VM. Теперь, осталось понять, а что представляет из себя корневая точка (GC Root)? «Источники» говорят, что существуют следующие типы корневых точек :
- Основной Java поток.
- Локальные переменные в основном методе.
- Статические переменные основного класса.
Таким образом, простое java-приложение будет иметь следующие корневые точки:
- Параметры main метода и локальные переменные внутри main метода.
- Поток, который выполняет main.
- Статические переменные основного класса, внутри которого находится main метод.
Очистка памяти
Имеется несколько подходов к очистке памяти, которые в совокупности определяют принцип функционирования Garbage Collection.
Copying collectors
При использовании «Copying collectors» область памяти делится на две части : в одной части размещаются объекты, а вторая часть остается чистой. На время очистки мусора приложение останавливает работу и запускается сборщик мусора, который находит в первой области объекты со ссылками и переносит их во вторую (чистую) область. После этого, первая область очищается от оставшихся там объектов без ссылок, и области меняются местами.
Главным достоинством данного подхода является плотное заполнение памяти. Недостатком «Copying collectors» является необходимость остановки приложения и размеры двух частей памяти должны быть одинаковыми на случай, когда все объекты остаются «живыми».
Данный подход в чистом виде в HotSpot VM не используется.
Mark-and-sweep
При использовании «mark-and-sweep» все объекты размещаются в одном сегменте памяти. Сборка мусора также приостанавливает приложение, и Garbage Collection проходит по дереву объектов, помечая занятые ими области памяти, как «живые». После этого, все не помеченные участки памяти сохраняются в «free list», в которой будут, после завершения сборки мусора, размещаться новые объекты.
К недостаткам данного подхода следует отнести необходимость приостановки приложения. Кроме этого, время сборки мусора, как и время приостановки приложения, зависит от размера памяти. Память становится «решетчатой», и, если не применить «уплотнение», то память будет использоваться неэффективно.
Данный подход также в чистом виде в HotSpot VM не используется.
Generational Garbage Collection
JVM HotSpot использует алгоритм сборки мусора типа «Generational Garbage Collection», который позволяет применять разные модули для разных этапов сборки мусора. Всего в HotSpot реализовано четыре сборщика мусора :
- Serial Garbage Collection
- Parallel Garbage Collection
- CMS Garbage Collection
- G1 Garbage Collection
Serial Garbage Collection относится к одним из первых сборщиков мусора в HotSpot VM. Во время работы этого сборщика приложение приостанавливается и возобновляет работу только после прекращения сборки мусора. В Serial Garbage Collection область памяти делится на две части («young generation» и «old generation»), для которых выполняются два типа сборки мусора :
- minor GC – частый и быстрый c областью памяти «young generation»;
- mark-sweep-compact – редкий и более длительный c областью памяти «old generation».
Область памяти «young generation», представленная на следующем рисунке, разделена на две части, одна из которых Survior также разделена на 2 части (From, To).

Алгоритм работы minor GC
Алгоритм работы minor GC очень похож на описанный выше «Copying collectors». Отличие связано с дополнительным использованием области памяти «Eden». Очистка мусора выполняется в несколько шагов :
- приложение приостанавливается на начало сборки мусора;
- «живые» объекты из Eden перемещаются в область памяти «To»;
- «живые» объекты из «From» перемещаются в «To» или в «old generation», если они достаточно «старые»;
- Eden и «From» очищаются от мусора;
- «To» и «From» меняются местами;
- приложение возобновляет работу.

В результате сборки мусора картинка области памяти изменится и будет выглядеть следующим образом :

Некоторые объекты, пережившие несколько сборок мусора в области From, переносятся в «old generation». Следует, также отметить, что и «большие живые» объекты могут также сразу же пеместиться из области Eden в «old generation» (на картинке не показаны).
Алгоритм работы mark-sweep-compact
Алгоритм «mark-sweep-compact» связяан с очисткой и уплотнением области памяти «old generation».

Принцип работы «mark-sweep-compact» похож на описанный выше «Mark-and-sweep», но добавляется процедура «уплотнения», позволяющая более эффективно использовать память. В процедуре живые объекты перемещаются в начало. Таким образом, мусор остается в конце памяти.
При работе с областью памяти используется механизм «bump-the-pointer», определяющий указатель на начало свободной памяти, в которой размещается создаваемый объект, после чего указатель смещается. В многопоточном приложении используется механизм TLAB (Thread-Local Allocation Buffers), который для каждого потока выделяет определенную область памяти.
Algo & DMA: технологии биржевой торговли
Биржевые технологии, алгоритмическая торговля, Java и программирование

Сборщики мусора в Java
Создатели JVM Hotspot понимали, что для разных приложений и разных ситуаций требуются разные сборщики мусора. Поэтому в JVM их несколько, и они отличаются своим поведением, скоростью работы и методами сбора мусора. Более того, так как в HotSpot JVM heap делится на несколько поколений, для сбора мусора в каждом из поколений могут использоваться разные сборщики.
Выбор и настройка сборщиков мусора осуществляется с помощью стартовых параметров, которых так много и написано о них столько, что проще дать ссылки.
Если не использовать эти опции, JVM включает дефолтный сборщик мусора (GC, garbage collector). В разных версиях Hotspot JVM он разный:
Характеристики
Каждый конкретный сборщик мусора имеет отличительные характеристики по следующим категориям:
- по работе алгоритма: Serial или Parallel. Serial для работы требует один поток. Parallel — работает в нескольких потоках.
- по влиянию на приложение: Concurrent или Stop-the-World. Concurrent работает в фоне без остановки Java-приложения. Stop-the-World для работы требует остановки всех потоков JVM.
- по алгоритму сборки мусора: Compacting или Non-compacting или Copying.
Serial GC
Первый, примитивный. Прямой как палка. Для сборки мусора полностью останавливает JVM и собирает мусор в один поток.
Parallel GC
Попытка сделать сборку быстрой и многопоточной. На многоядерных процессорах это хорошо работает. Но все равно для сборки он требует полного останова JVM. Использовался по умолчанию до Java 9. В новых версиях Java он продолжает совершенствоваться и дополняться новыми опциями, так что для каких-то особых приложений можно не обращаться к новым сборщикам мусора. Справится и улучшенный Parallel GC.
CMS GC
Цель этого GC — отсрочить полную сборку мусора с остановом JVM путем запуска маленьких и коротких операций по сбору сведений о мусоре, а потом путём быстрых и коротких операций — убрать, что можно. Если достигается определенный процент использованного heap, т.е. CMS-сборщик видит, что не справляется, то JVM останавливается и происходит полная сборка мусора.
Этот сборщик мусора как правило включают в тяжелых долгоработающих приложениях, которым выделяется много памяти под heap, где допускаются короткие задержки на быстрые легкие стадии сборки мусора, а длинные stop-the-world паузы желательны лишь в редких критических случаях. Типичный пример: J2EE AppServer, который работает круглые сутки и не перегружается месяцами, клиентское Swing/SWT-приложение, тяжелый критический API-сервис, ответы от которого критичны по времени.
На замену ему уже давно вышел сборщик мусора GCG1. В Java 8 сборщик CMS обозначен как deprecated, а в скором будущем CMS будет выпилен из JVM вообще и про него можно будет благополучно забыть.
G1 GC
Впервые появился как экспериментальный в JDK6u14, а начиная с JDK7 Update 4 (JDK 7u4) — как официальный. В Java 9 он теперь включен по умолчанию. Мейнстрим на несколько релизов Java вперед. Поколенческий, как и все предыдущие сборщики, но зоны поколений реализованы несколько иначе. Постоянно совершенствуется. В каждой новой версии Java добавляются новые функции.
- A (Re)Introduction to the G1 Garbage Collector by Paul Su
- G1GC Concepts and Performance Tuning
Shenandoah GC
Добавили в Java 12 в качестве экспериментального. Бэкпорты перенесены в Java 8 и Java 11 LTS для тех, кто желает пощупать, но не может перейти на Java 12. Разрабывается в компании RedHat (которую недавно купила IBM) Шипилёвым.
Включается следующими опциями в версиях Java до Java 15:
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoah
В Java 15 теперь он теперь официальный и готов к PROD. В Java 15 -XX:+UnlockExperimentalVMOptions уже не требуется.
ZeroGC
Для очень больших хипов — до нескольких терабайт. Цель — гарантировать паузы на сборку мусора на таком хипе максимум 10 миллисекундами. Появился в Java 11 как экспериментальный. Включается следующими опциями до Java 15:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
В Java 15 теперь он теперь официальный и готов к PROD. В Java 15 -XX:+UnlockExperimentalVMOptions не требуется.
Ниже представлена коллекция ссылок на видео, посвященные сборщику мусора ZGC. Список будет пополняться по мере появления нового об этом сборшике мусора.
- ZGC — Java’s Highly Scalable Low-Latency Garbage Collector
- ZGC: The Future of Low-Latency Garbage Collection Is Here
- ZGC: The Next Generation Low-Latency Garbage Collector
- Z Garbage Collector: The Next Generation
Epsilon GC
Экспериментальный. Не собирает мусор, а только аллоцирует объекты. Предназначен для проверок и замеров. Ну и для тех случаев, когда у вас уже всё настолько вылизано в коде, что осталось только отключить сборщик мусора, чтобы достичь Low-latency нирваны.
Включается следующими оцпиями:
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
Выводы
Итак, в текущей версии Java имеется три (ТРИ!) высокопроизводительных сборщика мусора: G1, Zero GC и Shenandoah. Два из них — Zero GC и Shenandoah — экспериментальные (а в Java 15 — уже полноценные), а G1 — готовый для употребления на PROD. Причем все три постоянно будут совершенствоваться с каждым новым 6-месячным релизом Java. Бесплатно.
Разработчики JDK призывают обновлять свои системы до свежих версий, потому что в каждой новой версии JDK работа сборщиков мусора от версии к версии улучшается, ускоряется и добавляются новые возможности и исправляются досадные ошибки.
Для тех, кому нужна еще большая производительность сборщика мусора, могут обратиться к Zing VM, которую предлагает компания Azul Systems. Она позиционирует свою VM и встроенный в нее сборщик мусора C3 — как специально заточеные под чувствительные к latency приложения. Но — за деньги.
Либо, писать Java приложения так, чтобы и бесплатный сборщик мусора справлялся.
Литература
- Real-Time Java Programming with Java RTS — книга посвящена «устаревшей» технологии RTS Java, но первые главы книги подробно описывают реализации сборщиков мусора в JVM HotSpot: Serial, Parallel и CMS, которые существовали тогда в JVM на момент написания книги (2009 год). Если вам по какой-то причине надо глубоко разобраться, как эти сборщики работают, это книга вам поможет.
- Java Performance Companion — книга посвящена подробному описанию работы G1 GC
- Garbage Collection: Algorithms for Automatic Dynamic Memory Management — классический труд (издан в 1996 году) на тему сбора мусора, в котором описаны все алгоритмы, реализованные позже в разных виртуальных машинах, включая JVM. Обновленнное спустя 20 лет издание 2016 года называется The Garbage Collection Handbook: The Art of Automatic Memory Management дает обзор технологий автоматической сборки мусора в исторической перспективе.
Ссылки по теме
- Список опций для настройки сборщика мусора в Java 8
- Understanding Java Garbage Collection — лекция Gil Tene, основателя компании Azul, о том, как работает сборщик мусора в Java
- G1: One Garbage Collector To Rule Them All — подробное описание работы G1 GC
Youtube
- 2017.12: Алексей Шипилёв — Shenandoah: сборщик мусора, который смог — презентация о Shenandoah GC от ее автора.
- 2022.05.02: JDK 8 to JDK 18 in Garbage Collection: 10 Releases, 2000+ Enhancements — про улучшения G1 GC на протяжении всех релизов Java.
Что такое сборщик мусора в Java
Узнайте, что такое сборщик мусора в Java, как он работает и какие алгоритмы использует для эффективного управления памятью.
Алексей Кодов
Автор статьи
9 июня 2023 в 16:31Сборщик мусора (Garbage Collector, GC) является важной частью системы управления памятью в Java. Он автоматически освобождает память, удаляя объекты, которые больше не используются программой. Это помогает предотвратить утечки памяти и обеспечивает более эффективное использование ресурсов.
Как работает сборщик мусора
Сборщик мусора в Java работает на основе принципа достижимости. Объект считается достижимым, если он доступен через ссылку из корневого набора ссылок (например, из статических полей, локальных переменных или активных стек-фреймов). Если объект недостижим, считается, что он больше не нужен, и его память может быть освобождена.
Процесс сборки мусора состоит из двух основных этапов:
- Маркировка (Marking): Во время этого этапа сборщик мусора определяет, какие объекты являются достижимыми и какие нет. Он проходит по дереву ссылок, начиная с корневых ссылок, и маркирует все достижимые объекты.
- Очистка (Sweeping): На этом этапе сборщик мусора удаляет недостижимые объекты и освобождает память, занятую ими. В зависимости от алгоритма сборки мусора, это может включать перемещение достижимых объектов и сжатие памяти для более эффективного использования.
Java-разработчик: новая работа через 11 месяцев
Получится, даже если у вас нет опыта в IT
Алгоритмы сборки мусора
В Java существует несколько алгоритмов сборки мусора, которые можно выбрать в зависимости от требований к производительности и памяти. Некоторые из наиболее распространенных алгоритмов включают:
- Serial GC: Использует однопоточную сборку мусора, подходит для небольших приложений с ограниченными ресурсами.
- Parallel GC: Использует многопоточную сборку мусора для ускорения процесса, подходит для приложений с большим объемом памяти и многопроцессорными системами.
- Concurrent Mark Sweep (CMS) GC: Осуществляет сборку мусора параллельно с работой приложения, уменьшая паузы, связанные с очисткой памяти. Подходит для приложений с высокими требованиями к отзывчивости.
- G1 (Garbage First) GC: Высокопроизводительный алгоритм сборки мусора, который сосредоточен на минимизации пауз и управлении большими кучами памяти.
Пример использования сборщика мусора
Допустим, у вас есть следующий код:
public class Example < public static void main(String[] args) < String str1 = new String("Hello"); String str2 = new String("World"); str1 = null; >>В этом примере сначала создается объект str1 со значением «Hello». Затем создается объект str2 со значением «World». После этого str1 устанавливается в null , делая объект «Hello» недостижимым. Сборщик мусора может определить, что этот объект больше не используется, и освободить память, занятую им.
Сборщик мусора в Java значительно упрощает управление памятью и предотвращает утечки памяти, но важно понимать его принципы работы и алгоритмы для эффективного использования ресурсов системы.