Вопрос 17 · Раздел 4

Что такое WeakHashMap?

4. Value → Key ссылки → утечка! 5. Вызывайте методы регулярно для очистки 6. Heap Dump → ищите Entry с null referent

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

WeakHashMap — Map, где ключи могут автоматически удаляться, когда на них больше нет ссылок.

Простая аналогия: Вы кладёте вещи на полку. Если вы забыли про вещь и никому она не нужна → уборщик её выбрасывает.

WeakHashMap<Object, String> map = new WeakHashMap<>();

Object key = new Object();
map.put(key, "value");

key = null;  // Больше нет ссылок на ключ!
System.gc(); // System.gc() — лишь рекомендация, в реальности удаление произойдёт при следующем GC

map.size();  // → 0 (запись удалена!)

🟡 Middle Level

Как это работает

WeakReference — ссылка, не мешающая GC удалить объект. Если на объект есть только WeakReference — GC его удалит.

ReferenceQueue — очередь, куда GC кладёт «умершие» ссылки. WeakHashMap периодически проверяет эту очередь.

expungeStaleEntries() — внутренний метод WeakHashMap, удаляющий записи с мёртвыми ключами при каждой операции (get, put, size).

1. Ключ обёрнут в WeakReference
2. При GC: если нет сильных ссылок → ключ удаляется
3. Entry попадает в ReferenceQueue
4. При следующем вызове ANY метода map:
   → expungeStaleEntries() очищает очередь
   → Запись удаляется из мапы

Ловушки

1. String literals не удаляются:

// ❌ Никогда не удалится!
WeakHashMap<String, String> map = new WeakHashMap<>();
map.put("literal", "value");  // Литерал живёт вечно в String Pool!

2. Strong reference из Value в Key:

// ❌ Утечка!
class Value { Object keyRef; }

map.put(key, new Value(key));  // Value держит ключ!
// Ключ никогда не удалится!

Когда использовать

// ✅ Метаданные для объектов
WeakHashMap<Component, String> tooltips = new WeakHashMap<>();
tooltips.put(button, "Click me");
// Когда button удалён → tooltip удалится автоматически

// ✅ НЕ для кэшей!
// → WeakReference очищается при КАЖДОМ Minor GC
// → Minor GC — сборка мусора в Young Generation, происходит каждые секунды-миллисекунды при активном создании объектов.
// → Для кэшей: Caffeine или SoftReference

// **Почему НЕ для кэшей:** WeakHashMap удаляет записи при КАЖДОМ Minor GC.
// Если вы закэшируете результат дорогого вычисления, он может исчезнуть через миллисекунды.
// Для кэшей используйте WeakHashMap только если ключи — уникальные объекты-идентификаторы, а не данные.
// Или лучше — java.lang.ref.SoftReference (не удаляется при Minor GC).

🔴 Senior Level

ReferenceQueue механизм

// WeakHashMap внутри:
class Entry<K,V> extends WeakReference<K> {
    V value;
    Entry<K,V> next;
    
    Entry(K key, V value, ReferenceQueue<K> queue) {
        super(key, queue);  // Queue для уведомлений
        this.value = value;
    }
}

// GC обнаружил weak-only ключ
// → Помещает Entry в ReferenceQueue
// → expungeStaleEntries() при следующем вызове map

Lazy очистка

// WeakHashMap НЕ очищается сам по себе!
// Очистка происходит при вызове ЛЮБОГО метода:

map.put(new Object(), "x");
// ... GC удалил ключ ...
// map всё ещё содержит "мёртвую" запись!

map.size();  // → Только тут очистится!

// → Если долго не вызывать методы → утечка!

WeakHashMap vs альтернативы

Решение Когда
WeakHashMap Lifecycle привязан к ключу
Caffeine Cache Полноценный кэш (LRU, TTL)
SoftReference Кэш, очистка при OOM угрозе
ThreadLocal Данные привязаны к потоку

Отличие от Map<WeakReference<K>, V>: WeakHashMap автоматически удаляет мёртвые записи — вам не нужно вручную чистить ReferenceQueue. В Map<WeakReference<K>, V> мёртвые ссылки копятся до бесконечности.

Диагностика утечек

Heap Dump: ищите Entry с referent = null
  → Entry всё ещё в table массиве
  → Методы мапы долго не вызывались
  → "Ленивая очистка" не сработала
  
Решение: map.size() принудительно очистит

Production Experience

Реальный сценарий: Component metadata

// UI фреймворк: мета-данные для компонентов
WeakHashMap<Component, Properties> meta = new WeakHashMap<>();

// Компонент удалён → meta автоматически чистится
// → Нет утечек памяти!

Best Practices

  1. Метаданные — основное применение
  2. НЕ кэш → слишком агрессивная очистка
  3. String literals → никогда не удалятся
  4. Value → Key ссылки → утечка!
  5. Вызывайте методы регулярно для очистки
  6. Heap Dump → ищите Entry с null referent

Резюме для Senior

  • WeakHashMap = авто-удаление по lifecycle ключа
  • ReferenceQueue → lazy очистка при вызове методов
  • String literals → не удаляются (String Pool)
  • Value → Key → strong reference = утечка
  • НЕ кэш → используйте Caffeine
  • Метаданные → основное применение
  • Heap Dump → Entry с null referent

🎯 Шпаргалка для интервью

Обязательно знать:

  • WeakHashMap — ключи обёрнуты в WeakReference, удаляются автоматически когда нет сильных ссылок
  • ReferenceQueue — GC кладёт «умершие» ссылки, WeakHashMap проверяет очередь при вызове любого метода
  • expungeStaleEntries() — lazy очистка, вызывается при get/put/size — если долго не вызывать, утечка памяти
  • String literals НЕ удаляются — живут вечно в String Pool
  • Strong reference из Value в Key = утечка! Key никогда не будет собран GC
  • Основное применение: метаданные (tooltip, свойства UI-компонентов), НЕ кэши
  • Для кэшей: Caffeine (LRU, TTL) или SoftReference (не удаляется при Minor GC)
  • Отличие от Map<WeakReference, V>: WeakHashMap автоматически чистит мёртвые записи, вручную не нужно

Частые уточняющие вопросы:

  • Почему WeakHashMap НЕ подходит для кэшей? — Удаляет записи при КАЖДОМ Minor GC. Закэшированный результат дорогого вычисления может исчезнуть через миллисекунды.
  • Как принудительно очистить WeakHashMap? — Вызвать любой метод: size(), get(), put(). expungeStaleEntries() проверит ReferenceQueue и удалит мёртвые записи.
  • Что будет если Value ссылается на Key? — Strong reference из Value не даст Key быть собранным GC — запись никогда не удалится, утечка памяти.
  • Почему String literals не удаляются из WeakHashMap? — Строковые литералы живут в String Pool — на них всегда есть сильная ссылка от ClassLoader.

Красные флаги (НЕ говорить):

  • ❌ «WeakHashMap — хороший кэш» — слишком агрессивная очистка, используйте Caffeine
  • ❌ «WeakHashMap очищается сам по себе в фоне» — очистка только при вызове методов (lazy)
  • ❌ «String literals удаляются из WeakHashMap» — нет, они живут в String Pool вечно
  • ❌ «WeakReference и SoftReference — одно и то же» — SoftReference не удаляется при Minor GC, только при OOM угрозе

Связанные темы:

  • [[14. Что такое Map и какие реализации существуют]]
  • [[15. В чём разница между HashMap, LinkedHashMap и TreeMap]]
  • [[18. Что такое ConcurrentHashMap]]