Питання 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]]