W3docs

Типы ссылок в 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 уведомляет вас о каждом «смерти».

java— editable, runs on the server

Что следует вынести из запуска:

  • Сильная ссылка вывела один и тот же 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, использованного выше.

Практика

Практика
Вам нужен кеш, который хранит вычисленные значения для ускорения программы, но должен автоматически освобождать их, а не вызывать OutOfMemoryError при нехватке памяти. Какой тип ссылок подходит лучше всего?
Вам нужен кеш, который хранит вычисленные значения для ускорения программы, но должен автоматически освобождать их, а не вызывать OutOfMemoryError при нехватке памяти. Какой тип ссылок подходит лучше всего?
Was this page helpful?