Мы все знаем о функциональных обязанностях сборщика мусора [GC] в Java. Но лишь немногие пытаются глубоко разобраться в том, как работает сборщик мусора. Вы не один из них, и поэтому вы здесь.
В этом руководстве по управлению памятью Java мы попытаемся разобраться в современных алгоритмах сборки мусора Java и рассмотрим эволюцию этих алгоритмов.
1. Управление памятью в Java
Управление памятью в Java — это обязанность сборщика мусора. Это противоположно тому, что было до Java, когда программисты отвечали за выделение и освобождение памяти в программах.
Формально говоря, сборщик мусора несет ответственность за –
- выделение памяти
- обеспечение того, чтобы все указанные объекты оставались в памяти, и
- восстановление памяти, используемой объектами, которые больше не доступны по ссылкам при выполнении кода.
Во время выполнения приложения приложения создают множество объектов, и каждый объект имеет свой жизненный цикл. Внутри памяти объекты, на которые ссылаются другие объекты, называются живыми объектами. Объекты, на которые больше не ссылается ни один живой объект, считаются мертвыми объектами и называются мусором. Процесс поиска и освобождения(также известный как восстановление) пространства, используемого этими объектами, называется сборкой мусора.
Сборка мусора решает многие, но не все, проблемы распределения памяти. Например, мы могли бы создавать объекты бесконечно и продолжать ссылаться на них до тех пор, пока не закончится доступная память( ошибка Out of memory ). Сборка мусора — сложная задача, которая сама по себе требует времени и ресурсов. Она выполняется на пространстве, которое обычно выделяется из большого пула памяти, называемого кучей.
Время сбора мусора зависит от сборщика мусора. Обычно вся куча или ее часть собирается либо при заполнении, либо при достижении порогового процента занятости.
Виртуальная машина Java HotSpot включает четыре сборщика мусора начиная с J2SE 5.0. Все сборщики являются поколенческими. Мы узнаем больше о поколенческом GC в следующих разделах.
2. Механизм подсчета ссылок
Это был очень старый механизм GC из начальных версий. В технике подсчета ссылок каждый объект имеет счетчик количества указателей на него из других объектов и из стека. Каждый раз, когда новый объект ссылается на него, счетчик увеличивается на единицу. Аналогично, когда какой-либо объект теряет свою ссылку, счетчик уменьшается на единицу. Когда счетчик достигает ‘0’, объект может быть освобожден сборщиком мусора.
Главным преимуществом алгоритма подсчета ссылок является небольшой объем работы на запись памяти при выделении новому объекту. Но у него есть очень критическая проблема с циклами данных. Это означает, что когда на первый объект ссылается второй объект, а на второй ссылается первый объект(циклические ссылки), то счетчик никогда не достигает нуля, следовательно, они никогда не подвергаются сборке мусора.
3. Механизм разметки и подметания

Алгоритм «отметить и убрать» был первым разработанным алгоритмом сборки мусора, способным восстанавливать циклические структуры данных. В этом алгоритме GC сначала идентифицирует некоторые объекты как достижимые по умолчанию, которые обычно являются глобальными переменными и локальными переменными в стеке. Они называются живыми объектами.
На следующем этапе алгоритм начинает трассировку объектов с этих живых объектов и также отмечает их как живые. Эта процедура продолжается до тех пор, пока все объекты не будут проверены и отмечены как живые. Объекты, не отмеченные как живые после полной трассировки, считаются мертвыми объектами.
При использовании mark-and-sweep неиспользуемые объекты не удаляются немедленно. Вместо этого сборка мусора может накапливаться до тех пор, пока не будет исчерпана вся доступная память. Когда это происходит, выполнение программы временно приостанавливается(это называется stop the world), пока алгоритм mark-and-sweep собирает весь мусор. После того, как все неиспользуемые объекты будут удалены, нормальное выполнение программы может возобновиться.
Этот метод, помимо приостановки работы приложения на некоторое время, требует частой дефрагментации адресного пространства памяти, что является еще одной накладной статьей.
4. Остановка и копирование GC
Подобно «отметить и смахнуть», этот алгоритм также зависит от идентификации живых объектов и их маркировки. Разница заключается в том, как он обрабатывает живые объекты.
Техника остановки и копирования представляет всю кучу в двух полупространствах. Только одно полупространство активно в каждый момент времени, и распределение памяти для вновь созданных объектов происходит только в одном полупространстве, в то время как другие остаются спокойными.
Когда GC запускается, он начинает отмечать живые объекты в текущем полупространстве, а когда это заканчивается, он копирует все живые объекты в другое полупространство. Все оставшиеся объекты в текущем полупространстве считаются мертвыми и собираются мусором.
Как и предыдущий подход, он имеет некоторые преимущества, например, касается только живых объектов. Кроме того, не требуется фрагментации, поскольку при переключении полупространств выполняется сжатие памяти.
Главными недостатками этого подхода является необходимость удвоения объема необходимой памяти, поскольку в определенный момент времени используется только половина. Кроме того, требовалось останавливать мир при переключении полупространств.
5. Остановка и копирование поколений
Подобно технике «остановить и скопировать», она также делит память на полупространства, но теперь это три полупространства. Эти полупространства называются здесь поколениями. Таким образом, память в этой технике организована в три поколения — молодое поколение, старое поколение и постоянное поколение.
Большинство объектов изначально выделяются в молодом поколении. Старое поколение содержит объекты, пережившие некоторое количество сборок молодого поколения, а также некоторые большие объекты, которые могут быть выделены непосредственно в старом поколении. Постоянное поколение содержит объекты, которые JVM считает удобными для управления сборщиком мусора, например объекты, описывающие классы и методы, а также сами классы и методы.
Когда заполняется молодое поколение, выполняется сбор мусора молодого поколения(иногда называемый второстепенным сбором) только этого поколения. Когда заполняется старое или постоянное поколение, обычно выполняется то, что известно как полный сбор мусора(иногда называемый основным сбором). То есть собираются все поколения.
Обычно, молодое поколение собирается первым, используя алгоритм сбора мусора, разработанный специально для этого поколения, потому что это, как правило, самый эффективный алгоритм для идентификации мусора в молодом поколении. Объекты, которые выживают после трассировки GC, перемещаются в более старые поколения. Более старые поколения собираются реже по очевидным причинам, то есть они там, потому что они будут там дольше. Помимо вышесказанного, если происходит фрагментация/уплотнение, каждое поколение уплотняется отдельно.
Главные преимущества этого метода — раннее восстановление мертвых объектов в самом молодом поколении и отсутствие необходимости сканировать всю память каждый раз для идентификации мертвых объектов. Объекты старого поколения уже прошли несколько циклов GC, поэтому предполагается, что они находятся в системе дольше, поэтому нет необходимости сканировать их часто [не идеальный случай каждый раз, но в основном так должно быть].
Недостатки те же самые, то есть необходимость дефрагментации областей памяти и необходимость остановки мира(приложения) во время выполнения GC полного сканирования.
6. Как улучшить использование памяти в Java
- Не выделяйте избыточную память. Выделяйте память ровно столько, сколько нужно. Это особенно применимо к массивам Java.
- Не держитесь за ссылки. Как только объект использован и больше не нужен, присвойте ему нулевую ссылку.
- Найдите и устраните утечки памяти
- Выполняйте профилирование системы для каждого выпуска, чтобы проверить объемы памяти.
- Не полагайтесь на System.gc() для запуска сборки мусора
Надеюсь, это освежило для вас знания о механизмах сборки мусора, которые позволяют автоматически управлять памятью для программ Java. Это может помочь вам при ответе на вопросы по управлению памятью Java на собеседовании.