В мире Java разработчики редко пишут `delete` или `free()`. Память управляется автоматически, словно невидимым дворецким. Этот волшебный дворецкий — сборщик мусора (Garbage Collector, GC) — одна из ключевых фич JVM, которая делает Java безопасной и продуктивной. Но как он на самом деле отличает полезные данные от «хлама» и когда решает его вынести? Давайте заглянем под капот.
Основной принцип: достижимость объектов
Сердцевина работы GC — концепция достижимых объектов. JVM отслеживает так называемые «корни» (GC Roots): статические поля, локальные переменные в стеке, активные потоки и т.д. Любой объект, до которого можно добраться через цепочку ссылок, начиная с корня, считается живым. Всё остальное — мусор.
Ключевой момент: GC работает не с «мертвыми» объектами, а ищет живые. Всё, что не найдено в процессе этого поиска, подлежит удалению.
Поколения объектов и алгоритмы сборки
Большинство современных сборщиков используют поколенческую модель, основанную на эмпирическом наблюдении: большинство объектов умирают молодыми.
Молодое поколение (Young Generation)
Сюда попадают новосозданные объекты. Молодое поколение делится на:
- Eden (Эдем) — здесь рождаются все новые объекты.
- Survivor Spaces (S0 и S1) — два небольших пространства, куда перемещаются выжившие после «минорной сборки».
Когда Eden заполняется, запускается Minor GC — быстрая сборка, которая копирует живые объекты из Eden и одного Survivor в другой Survivor, увеличивая их «возраст». Объекты, достигшие определенного возраста, продвигаются в старое поколение.
Старое поколение (Old Generation)
Сюда попадают долгоживущие объекты. Сборка здесь — Major GC (или Full GC) — происходит реже, но длится дольше и может «остановить мир» (Stop-The-World).
Типы сборщиков мусора в HotSpot JVM
Java предлагает выбор алгоритмов под разные задачи:
- Serial GC — однопоточный, для приложений с малым объемом данных.
- Parallel GC (Throughput Collector) — использует несколько потоков для ускорения сборки в Young и Old поколениях. Цель — максимизировать пропускную способность.
- CMS (Concurrent Mark-Sweep) — старается минимизировать паузы, выполняя часть работы параллельно с приложением. Устарел в новых версиях.
- G1 (Garbage-First) — универсальный сборщик для больших куч, разбивающий память на регионы. Цель — достичь предсказуемых пауз.
- ZGC и Shenandoah — ультрасовременные низколатентные сборщики, стремящиеся к паузам не более 10 мс даже на терабайтных кучах.
Совет разработчику: Не пытайтесь «помогать» GC, обнуляя ссылки (`obj = null`) в конце метода. Современный JIT-компилятор и GC умнее. Фокус на архитектуре и избегании утечек через коллекции или слушатели событий.
Что такое «утечка памяти» в Java?
Да, они возможны! Если на объект остаётся ссылка, но он больше не нужен, GC не удалит его. Типичные сценарии:
- Статические коллекции, которые только пополняются.
- Неотписанные слушатели событий (listeners).
- Кэши без политики вытеснения.
- Внутренние классы, захватывающие ссылку на внешний контекст.
Как мониторить и настраивать GC?
Используйте флаги JVM:
- `-Xmx` и `-Xms` для размера кучи.
- `-XX:+UseG1GC` для выбора G1.
- `-Xlog:gc*` для детальных логов.
Инструменты: VisualVM, JConsole, GC logs анализаторы.
FAQ: Часто задаваемые вопросы
Можно ли принудительно вызвать сборку мусора?
System.gc() — это лишь вежливая просьба к JVM. Виртуальная машина может её проигнорировать. Не используйте в продакшене для управления памятью.
Почему GC вызывает паузы (Stop-The-World)?
Чтобы гарантировать консистентность при подсчёте ссылок и перемещении объектов. Современные сборщики (ZGC, Shenandoah) сводят эти паузы к минимуму.
Влияет ли finalize() на работу GC?
Да, и очень плохо. Метод finalize() замедляет сборку, объекты с ним попадают в специальную очередь и очищаются только через несколько циклов. Избегайте его использования.
Как выбрать сборщик для моего приложения?
Если приложение не latency-sensitive — Parallel GC. Для веб-сервисов с требованием к отклику — G1. Для сверхнизких задержек (финансовые системы) — ZGC или Shenandoah.