Сборщик мусора и метод finalize - Вопросы
Всего: 6 вопросов
1. Когда объект в Java становится доступным для сборки мусора и что такое GC roots?
Когда объект в Java становится доступным для сборки мусора и что такое GC roots?
Объект становится мусором, когда до него нельзя добраться ни по одной цепочке ссылок от корней сборки мусора (GC roots). Корнями являются: локальные переменные и параметры методов в стеках всех живых потоков, статические поля загруженных классов, сами живые объекты Thread, ссылки из нативного кода (JNI) и объекты, используемые как мониторы синхронизации. Важное следствие модели достижимости: циклические ссылки не мешают сборке. Если два объекта ссылаются друг на друга, но до них не дотянуться от GC roots, они образуют «остров изоляции» и будут собраны целиком. Это принципиальное отличие от подсчёта ссылок (reference counting), где такой цикл остался бы в памяти навсегда. Присваивание obj = null само по себе объект не удаляет — оно лишь убирает одну ссылку, а память освободит сборщик мусора и только если других ссылок не осталось.
2. Гарантирует ли вызов System.gc() сборку мусора?
Гарантирует ли вызов System.gc() сборку мусора?
Нет. System.gc() — это лишь подсказка (hint) виртуальной машине, а не команда. JVM может выполнить полную сборку, выполнить её позже или проигнорировать вызов совсем. Вызов Runtime.getRuntime().gc() эквивалентен: System.gc() просто делегирует ему работу. Более того, приложение можно запустить с флагом -XX:+DisableExplicitGC, и тогда все явные вызовы System.gc() превратятся в пустую операцию. В прикладном коде System.gc() вызывать не стоит: обычно это провоцирует дорогую полную сборку в режиме stop-the-world и делает поведение приложения хуже. Допустимые сценарии — учебные примеры, микробенчмарки и замеры памяти перед снятием heap dump.
3. Почему метод finalize() признали устаревшим и чем его заменить?
Почему метод finalize() признали устаревшим и чем его заменить?
Механизм финализации накопил слишком много проблем: непредсказуемая задержка между моментом, когда объект стал мусором, и вызовом метода; риск OutOfMemoryError из-за растущей очереди финализации (объект с finalize() переживает минимум одну лишнюю сборку); молча проглоченные исключения; возможность воскресить объект, записав this в статическое поле; гонки и атака finalizer attack; заметные накладные расходы на создание и сборку таких объектов. Хронология: в Java 9 метод помечен @Deprecated и добавлен java.lang.ref.Cleaner, в Java 11 удалён System.runFinalizersOnExit(), в Java 18 по JEP 421 финализация помечена deprecated for removal и появился ключ --finalization=disabled. Замена: основной способ — детерминированный close() через AutoCloseable и try-with-resources; Cleaner нужен только как страховка на случай, если close() так и не вызвали. Единственная гарантия спецификации для finalize(): если метод всё-таки будет вызван, то не более одного раза за время жизни объекта.
4. Чем отличаются strong, soft, weak и phantom ссылки в Java?
Чем отличаются strong, soft, weak и phantom ссылки в Java?
Пакет java.lang.ref позволяет управлять тем, насколько сильно ссылка удерживает объект от сборки. Strong (обычная ссылка, отдельного класса нет) — объект не будет удалён никогда, пока ссылка достижима от GC roots; так работает весь обычный код. Soft (SoftReference) — объект удаляется при нехватке памяти, до выброса OutOfMemoryError; подходит для кэшей, которые не жалко потерять. Weak (WeakReference) — объект удаляется при первой же сборке, если сильных ссылок на него нет; применяется в WeakHashMap, метаданных и слушателях. Phantom (PhantomReference) — метод get() всегда возвращает null, поэтому воскресить объект невозможно; уведомление приходит в ReferenceQueue уже после того, как объект признан недостижимым. На фантомных ссылках построен класс Cleaner, который и стоит использовать вместо ручной работы с очередью.
5. Какие сборщики мусора есть в HotSpot JVM и какой из них используется по умолчанию?
Какие сборщики мусора есть в HotSpot JVM и какой из них используется по умолчанию?
Начиная с Java 9 по умолчанию используется G1 (Garbage-First, флаг -XX:+UseG1GC): куча разбита на регионы, задаётся целевая длительность пауз, подходит большинству серверных приложений. В Java 8 по умолчанию был Parallel GC, а на слабых машинах JVM по эргономике может выбрать Serial GC. Остальные варианты: -XX:+UseSerialGC — однопоточный, минимальные накладные расходы, для маленькой кучи и контейнера с одним ядром; -XX:+UseParallelGC — максимальная пропускная способность ценой длинных пауз, для пакетной обработки; -XX:+UseZGC — паузы порядка долей миллисекунды на кучах в терабайты, для сервисов, чувствительных к задержкам; -XX:+UseShenandoahGC — уплотнение кучи параллельно с работой приложения; -XX:+UseEpsilonGC — не собирает ничего, память просто заканчивается, нужен для бенчмарков. Проверить текущий выбор можно командой java -XX:+PrintFlagsFinal -version или в логах запуска с ключом -Xlog:gc.
6. Бывают ли утечки памяти в Java, если есть сборщик мусора?
Бывают ли утечки памяти в Java, если есть сборщик мусора?
Да. Сборщик мусора удаляет только недостижимые объекты, поэтому утечка памяти в Java — это объект, который больше не нужен, но всё ещё достижим от GC roots. Типичные источники: растущие статические коллекции, кэши без вытеснения, неотписанные слушатели, ThreadLocal в потоке из пула, ключ в HashMap без корректных equals() и hashCode(), незакрытые ресурсы. Диагностируют такие утечки по heap dump в VisualVM или Eclipse MAT либо через Java Flight Recorder. Отдельно стоит помнить, что сборщик мусора управляет только кучей: метаданные классов с Java 8 живут в Metaspace, а память, выделенная через ByteBuffer.allocateDirect() или JNI, находится вне кучи, и её освобождение GC напрямую не контролирует.