Что такое soft, weak и phantom references в Java?
SoftReference удерживает объект до нехватки памяти (кеши), WeakReference не мешает GC (WeakHashMap, listeners), PhantomReference позволяет выполнить cleanup после финализации — объект уже недостижим, но ещё не освобождён.
Иерархия ссылок в Java
Все четыре типа находятся в пакете java.lang.ref. Сильная (strong) ссылка — обычная переменная; три остальных типа позволяют GC собирать объекты в разных сценариях.
SoftReference
GC собирает soft-объекты только при нехватке памяти (перед OutOfMemoryError). Идеальны для memory-sensitive кешей.
import java.lang.ref.SoftReference;
SoftReference<byte[]> cache = new SoftReference<>(new byte[1024 * 1024]);
byte[] data = cache.get(); // null, если GC уже собрал
if (data == null) {
data = loadFromDisk();
cache = new SoftReference<>(data);
}
WeakReference
GC собирает weak-объекты при первой возможности, если на них нет сильных ссылок. Применяются в WeakHashMap, кешах метаданных, listener-реестрах без утечки памяти.
import java.lang.ref.WeakReference;
WeakReference<ExpensiveObject> weakRef = new WeakReference<>(new ExpensiveObject());
// После следующего GC weakRef.get() может вернуть null
ExpensiveObject obj = weakRef.get();
if (obj != null) {
obj.doWork();
}
Пример реестра слушателей без утечки:
Map<Listener, Boolean> listeners = new WeakHashMap<>();
listeners.put(myListener, Boolean.TRUE);
// Когда myListener не имеет других сильных ссылок, запись автоматически удаляется
PhantomReference
PhantomReference.get() всегда возвращает null. Объект помещается в ReferenceQueue после финализации, но до освобождения памяти. Используется для детерминированного cleanup ресурсов (альтернатива finalize()).
import java.lang.ref.*;
ReferenceQueue<Resource> queue = new ReferenceQueue<>();
Resource resource = new Resource();
PhantomReference<Resource> phantom = new PhantomReference<>(resource, queue);
resource = null; // убираем сильную ссылку
// В отдельном потоке мониторинга:
Reference<?> ref = queue.remove(); // блокирует до постановки в очередь
if (ref == phantom) {
cleanupNativeResources();
}
Сравнение поведения
- Strong: объект живёт, пока есть хоть одна сильная ссылка.
- Soft: GC собирает при
-Xmxpressure; JVM гарантирует очистку до OOM. - Weak: GC собирает в ближайший цикл без strong-ссылок.
- Phantom:
get()== null всегда; уведомление черезReferenceQueueпосле финализации.
Подводные камни
- SoftReference и производительность: JVM по умолчанию держит soft-ссылки до 1 секунды на каждый МБ свободной кучи (
-XX:SoftRefLRUPolicyMSPerMB). На серверах с большой кучей кеш может занимать гигабайты. - WeakHashMap и ключи: ключи WeakHashMap слабо ссылаются, но значения — сильно. Если значение ссылается на ключ, утечка памяти не устраняется.
- Финализаторы и PhantomReference: если у объекта есть
finalize(), он может «воскреснуть» (resurrect), создав новую сильную ссылку. PhantomReference ставится в очередь уже после финализации. - ReferenceQueue обязателен для PhantomReference: создание PhantomReference без очереди бессмысленно —
get()всегда null, и отслеживать освобождение невозможно. - Не используйте finalize(): метод устарел в Java 9, удалён в Java 18+. Для cleanup используйте
Cleaner(Java 9+) поверх PhantomReference. - Cleaner API как правильная альтернатива:
java.lang.ref.Cleanerинкапсулирует PhantomReference + ReferenceQueue и запускает действие в выделенном потоке. - Производительность GC: большое количество Reference-объектов замедляет GC — он вынужден обрабатывать reference queues на каждом цикле.
Common mistakes
- Путать термин «reference types» с соседним механизмом Java.
- Не называть границу lifecycle, transaction, thread или request для «reference types».
- Игнорировать production-эффекты «reference types»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «reference types» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «reference types».
- Уточнить, какие настройки или API меняют «reference types» в реальном сервисе.