Представьте, что вы пишете программу на Java, создаёте тысячи объектов, но никогда вручную не освобождаете память. Никаких `free()` или `delete`, как в C++. Это не магия, а одна из ключевых фишек Java — автоматическая сборка мусора (Garbage Collection, GC). Это умный и сложный механизм, работающий в фоновом режиме, который находит и удаляет объекты, ставшие программе ненужными, предотвращая утечки памяти и делая разработку безопаснее. Давайте заглянем под капот JVM и разберёмся, как этот невидимый уборщик поддерживает порядок в куче (heap).
Основной принцип: Достижимость объектов
Вся философия сборки мусора строится на концепции достижимости. JVM отслеживает, на какие объекты есть ссылки из так называемых «корней» (Garbage Collection Roots). К ним относятся:
- Локальные переменные в стеке потоков.
- Статические переменные классов.
- Ссылки внутри JNI (Java Native Interface).
- Активные потоки выполнения.
Если от этих корней по цепочке ссылок нельзя добраться до какого-либо объекта в куче, он считается «мусором» и подлежит удалению. Объект не удаляется мгновенно в момент обнуления последней ссылки — решение принимает сборщик в своё время.
Важно: Сборщик мусора работает только с кучей (heap). Память в стеке (локальные примитивы и ссылки) освобождается автоматически при выходе из метода.
Поколения объектов и алгоритмы сборки
Большинство современных сборщиков в HotSpot JVM используют поколенческую модель (Generational Hypothesis). Она основана на наблюдении: большинство объектов живут очень недолго («умирают молодыми»).
Разделение кучи на поколения
- Young Generation (Молодое поколение): Сюда помещаются новосозданные объекты. Разделена на Eden (Эдем) и два Survivor Space (S0 и S1).
- Old Generation (Старое поколение или Tenured): Сюда попадают объекты, пережившие несколько сборок в Young Generation.
- Permanent Generation / Metaspace (для метаданных классов): Хранит информацию о загруженных классах. В Java 8 Permanent Gen заменён на Metaspace, которая живёт вне кучи.
Процесс сборки в деталях
- Создание объекта: Новый объект создаётся в Eden.
- Minor GC (Малая сборка): Когда Eden заполняется, запускается быстрая сборка только в Young Generation.
- Все достижимые объекты из Eden и одного Survivor-пространства копируются во второй Survivor.
- Недостижимые объекты удаляются.
- Объекты, пережившие определённое количество таких циклов (по умолчанию 15), перемещаются в Old Generation.
- Major GC / Full GC (Полная сборка): Происходит реже, но гораздо дороже. Собирает мусор во всей куче (и Young, и Old), часто сопровождается остановкой всех прикладных потоков (STW — Stop-The-World).
Типы сборщиков мусора в HotSpot JVM
Java предлагает выбор алгоритмов GC под разные задачи:
- Serial GC: Простой однопоточный сборщик для небольших приложений.
- Parallel GC (Throughput Collector): Многопоточная версия для Young Generation, цель — максимизировать пропускную способность. Стандарт в Java 8.
- CMS (Concurrent Mark-Sweep): Старался минимизировать паузы, работая concurrently с приложением. Устарел (deprecated).
- G1 (Garbage-First): Современный сборщик по умолчанию с Java 9. Делит кучу на регионы, собирает в первую очередь регионы с наибольшим количеством мусора. Балансирует между пропускной способностью и паузами.
- ZGC и Shenandoah: Сверхсовременные low-pause сборщики, цель — паузы не более 10 мс даже на терабайтных кучах.
Совет разработчику: Не вызывайте System.gc() в продакшене! Это лишь «намёк» JVM, но сборка может не произойти, а производительность из-за неожиданной полной очистки может просесть.
Как писать код, дружественный к GC?
Понимание работы GC помогает писать эффективный код:
- Сокращайте время жизни объектов: Используйте локальные переменные в минимальной области видимости.
- Избегайте утечек через ссылки: Обнуляйте ссылки в коллекциях (
ArrayList,HashMap), когда объект больше не нужен. Особенно актуально для кэшей и слушателей событий. - Будьте осторожны с finalize(): Метод
finalize()ненадёжен, его вызов не гарантирован, он может «воскресить» объект и серьёзно замедлить сборку. ИспользуйтеAutoCloseableи try-with-resources. - Выбирайте подходящий GC: Для веб-сервиса с низкими задержками — G1 или ZGC. Для пакетной обработки данных — Parallel GC.
FAQ: Часто задаваемые вопросы
Гарантирует ли GC отсутствие утечек памяти?
Нет. GC удаляет только недостижимые объекты. Если вы храните ссылку на ненужный объект в статическом поле или кэше — это утечка. GC бессилен.
Можно ли принудительно запустить сборку мусора?
Можно «попросить» через System.gc(), но JVM может проигнорировать запрос. Гарантии нет.
Почему Full GC такая медленная?
Потому что она проверяет и обрабатывает все живые объекты во всей куче, что требует много вычислений и часто приводит к компактизации памяти (уплотнению).
Влияет ли null на работу GC?
Присвоение null ссылке удаляет одну из возможных цепочек достижимости. Это может помочь объекту стать мусором раньше, но обычно в этом нет необходимости — доверяйте алгоритмам JVM.
Как мониторить работу GC?
Используйте JVM-флаги: -Xlog:gc*, -XX:+PrintGCDetails, а также инструменты вроде VisualVM, JConsole или современные APM-решения (Prometheus, Grafana с JMX).