Типы ссылок в Java: сильные, слабые, мягкие, фантомные
Как типы ссылок в Java взаимодействуют с garbage collector для создания кешей, слушателей и очистки ресурсов.
Сборка мусора кажется автоматической, но Java даёт вам инструмент для влияния на неё. Обычная переменная — это сильная ссылка, которая удерживает объект в памяти. Пакет java.lang.ref добавляет три более слабые степени — мягкую, слабую и фантомную, — которые позволяют сборщику мусора освобождать объект, даже если вы всё ещё держите на него ссылку. Эти типы ссылок лежат в основе памяти-чувствительных кешей, регистров слушателей без утечек и надёжной очистки ресурсов.
Достижимость определяет, кто живёт
Сборщик мусора сохраняет объект живым, пока он достижим из корня GC (активный поток, статическое поле, переменная стека). Сила ссылок на пути к объекту определяет класс его достижимости, а тот решает его судьбу при нехватке памяти.
| Ссылка | get() возвращает объект? | GC сохраняет её живой? | Типичное применение |
|---|---|---|---|
| Сильная | Всегда | Всегда (пока сильно достижима) | Обычные переменные и поля |
| Мягкая | До нехватки памяти | До давления на кучу | Памяти-чувствительные кеши |
| Слабая | До следующего сбора GC | Нет | Канонизирующие отображения, слушатели |
| Фантомная | Никогда (всегда null) | Нет | Очистка после удаления |
Порядок от сильнейшей к слабейшей: сильная → мягкая → слабая → фантомная. Когда несколько ссылок разной силы указывают на один объект, побеждает сильнейшая — одной сильной ссылки достаточно, чтобы сохранять объект живым вечно.
Сильные ссылки: поведение по умолчанию
Каждая ссылка, созданная без API java.lang.ref, является сильной. Пока одна сильная ссылка достижима, объект не может быть собран — это источник большинства утечек памяти, когда забытая запись в долгоживущей коллекции удерживает объекты живыми бесконечно.
List<byte[]> cache = new ArrayList<>();
cache.add(new byte[10_000_000]); // 10 MB pinned by a strong reference
// Nothing here can be collected until 'cache' itself becomes unreachable.Мягкие ссылки: памяти-чувствительные кеши
SoftReference позволяет сборщику освобождать объект только при нехватке памяти в куче. Пока памяти достаточно, get() продолжает возвращать объект, что делает мягкие ссылки идеальным решением для кешей, которые должны уменьшаться под давлением, а не вызывать OutOfMemoryError.
SoftReference<BufferedImage> ref = new SoftReference<>(loadThumbnail(path));
BufferedImage cached = ref.get();
if (cached == null) { // GC reclaimed it under memory pressure
cached = loadThumbnail(path); // recompute and re-wrap
ref = new SoftReference<>(cached);
}
return cached;JVM гарантирует, что все мягкие ссылки на мягко-достижимые объекты будут очищены перед тем, как она выбросит OutOfMemoryError, поэтому кеш на мягких ссылках жертвуется последним, а не первым.
Слабые ссылки: отображения и слушатели
WeakReference вообще не откладывает сборку. Как только объект является лишь слабо достижимым, он становится кандидатом для GC, и get() вернёт null после следующего цикла сборки. Это именно то, что нужно для ключей в кеше, который должен исчезать, когда никто другой их не использует, — именно на этом построен WeakHashMap.
WeakHashMap<Widget, Metadata> sidecar = new WeakHashMap<>();
sidecar.put(widget, metadata);
// When 'widget' is no longer strongly referenced elsewhere, the entry
// vanishes automatically — no manual remove(), no leak.Сочетание ссылки с ReferenceQueue позволяет получать уведомления при сборке референта: GC помещает очищенную ссылку в очередь, чтобы вы могли выполнить дополнительную логику.
ReferenceQueue<Widget> queue = new ReferenceQueue<>();
WeakReference<Widget> ref = new WeakReference<>(widget, queue);
// later, on a cleanup thread:
Reference<?> dead = queue.remove(); // blocks until a referent is collectedФантомные ссылки: очистка после удаления
PhantomReference — самая слабая степень и самая специализированная. Её get() всегда возвращает null, поэтому воскресить объект через неё невозможно. Единственная её цель — быть поставленной в очередь после того, как объект был собран, предоставляя безопасный хук для освобождения нативных ресурсов — современная надёжная замена устаревшему finalize().
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(resource, queue);
// A background thread drains the queue and frees the off-heap buffer
// only once the JVM confirms the object is truly gone.Собственный java.lang.ref.Cleaner из JDK (Java 9+) построен на фантомных ссылках и именно его следует использовать в реальном коде вместо ручного управления очередью.
Рабочий пример: все четыре силы в одном запуске
Эта программа создаёт один объект, удерживаемый только каждым типом ссылки, принудительно запускает сборку мусора с помощью System.gc() и сообщает, что выжило. Она также подключает ReferenceQueues к слабой и фантомной ссылкам, чтобы вы могли наблюдать, как GC уведомляет вас о каждом «смерти».
Что следует вынести из запуска:
- Сильная ссылка вывела один и тот же
Resource(kept-alive)в начале и в конце. Несмотря на два промежуточных вызоваSystem.gc(), сильно достижимый объект никогда не является кандидатом на сборку — сильные ссылки всегда побеждают. weak.get()вернулResource(weakly-held)до GC, ноnullпосле. Как только единственная сильная ссылка (onlyWeaklyHeld) была установлена вnull, объект стал лишь слабо достижимым, поэтому следующая же сборка очистила слабую ссылку.weak ref enqueued? : trueподтверждает, что GC поместил очищеннуюWeakReferenceв еёReferenceQueue. Это помещение в очередь и есть механизм уведомления — именно такWeakHashMapи регистры слушателей узнают, что запись можно удалить.soft.get()всё ещё вернулResource(cache-entry)послеgc(). Куча не испытывала давления, поэтому сборщик сохранил мягко-достижимый объект живым — именно такое поведение делает мягкие ссылки подходящими для кешей, которые уменьшаются только при нехватке памяти.phantom.get()вывелnullдаже до какой-либо сборки, однакоphantom enqueued? : trueпоказывает, что он всё равно был поставлен в очередь после гибели своего референта. Фантомная ссылка никогда не возвращает объект; она существует исключительно для сигнала после факта о том, что очистка может безопасно выполняться.
Связанные темы
Силы ссылок имеют смысл только вместе с тем, как JVM возвращает память:
- Сборка мусора в Java — что означает «достижимость» и когда фактически запускается сборщик.
- Модель памяти Java — как объекты, потоки и видимость взаимодействуют друг с другом.
- Стек и куча в Java — где фактически живут объекты, на которые указывают ваши ссылки.
- HashMap в Java — аналог с сильными ключами для
WeakHashMap, использованного выше.