Питання 27 · Розділ 10

Що таке WeakHashMap?

WeakHashMap зберігає ключі як слабкі посилання (WeakReference): 4. "Мертві" записи видаляються з таблиці

Мовні версії: English Russian Ukrainian

🟢 Junior Level

WeakHashMap — це спеціальна Map, яка автоматично видаляє записи, коли на ключ більше немає посилань.

Проста аналогія: Уявіть, що ви прив’язуєте записки до повітряних куль. Коли куля відлітає (ключ збирає збирач сміття), записка теж зникає.

Приклад:

WeakHashMap<Object, String> map = new WeakHashMap<>();
Object key = new Object();
map.put(key, "Some data");

System.out.println(map.size()); // 1

key = null; // Більше немає посилань на ключ
System.gc(); // лише рекомендація — GC може не запуститися

System.out.println(map.size()); // 0 (якщо GC спрацював) або 1 (якщо ні)

Коли використовувати: Для кешування метаданих, прив’язаних до об’єктів, якими ви не керуєте.

🟡 Middle Level

Як це працює

WeakHashMap зберігає ключі як слабкі посилання (WeakReference):

  1. GC виявляє, що на ключ залишилося лише слабке посилання
  2. Поміщає посилання в ReferenceQueue
  3. При наступній операції над WeakHashMap викликається expungeStaleEntries()
  4. “Мертві” записи видаляються з таблиці

Коли WeakHashMap НЕ видалить записи

Ключ буде зібраний GC лише коли на нього немає strong посилань. Якщо хтоось ПОЗА WeakHashMap тримає strong reference на ключ — записи ніколи не видаляться:

Object key = new Object();
strongRef = key;  // хтось ще тримає посилання
map.put(key, "value");
key = null;
System.gc();
// Ключ НЕ видалений — strongRef все ще існує!
map.size(); // 1

Справжній memory leak: коли ви забули прибрати strong reference на ключ, а WeakHashMap “чекає” GC.

Сценарії використання

  1. Кешування метаданих — прив’язка даних до об’єктів з чужого коду
  2. Lapsed Listener — зберігання слухачів без запобігання GC
  3. Thread-local дані — альтернатива ThreadLocal

Чого WeakHashMap НЕ робить

Що WeakHashMap Справжній Cache (Caffeine)
Автоочищення по GC Так Ні
LRU/LFU eviction Ні Так
TTL (час життя) Ні Так
Розмір кешу Не обмежений Обмежений
Потокобезпечність Ні Так

Типові помилки

  1. Використання рядкових літералів як ключів — вони в String Pool, ніколи не видаляються
  2. Очікування миттєвого очищення — очищення ліниве, лише при звертанні
  3. Забутий strong reference — хтось ще тримає посилання на ключ, GC не видалить

🔴 Senior Level

Internal Implementation

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    int hash;
    Entry<K,V> next;

    Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
        super(key, queue); // Ключ — WeakReference з чергою
        this.value = value;
        this.hash = hash;
        this.next = next;
    }
}

ReferenceQueue Mechanism

// При кожній операції:
private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null;) {
        synchronized (queue) {
            Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);
            // Видаляємо Entry з ланцюжка
        }
    }
}

Очищення ліниве — лише при get, put, size, containsKey тощо.

Reachability і GC

Java визначає досяжність об’єктів:

  • Strong reachability: звичайні посилання → не видаляються
  • Soft reachability: через SoftReference → видаляються при нестачі пам’яті
  • Weak reachability: через WeakReference → видаляються при наступному GC
  • Phantom reachability: через PhantomReference → для post-mortem cleanup

WeakHashMap використовує Weak → об’єкт видалиться при наступному GC, навіть якщо пам’яті багато.

Edge Cases

  1. Integer Cache (-128 to 127): Integer-ключі з кешу ніколи не видаляються
  2. String Pool: Рядкові літерали — сильні посилання від JVM
  3. Thread-safety: WeakHashMap не потокобезпечна — потрібна зовнішня синхронізація

When NOT to Use

  • Production caching: Використовуйте Caffeine/Guava Cache з LRU + TTL
  • High-throughput: WeakHashMap повільніша за HashMap через ReferenceQueue polling
  • Concurrent access: Потрібна Collections.synchronizedMap(new WeakHashMap<>())

Production Monitoring

Для аналізу WeakHashMap:

  • Моніторинг size() — різке зменшення = GC activity
  • Heap dump показує ReferenceQueue з pending entries
  • JFR: GC events correlate з WeakHashMap cleanup

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • WeakHashMap автоматично видаляє записи коли на ключ немає strong посилань
  • Ключі зберігаються як WeakReference + ReferenceQueue; очищення ліниве (при get/put/size)
  • GC видаляє ключ лише коли немає strong references ПОЗА WeakHashMap
  • Сильне посилання на ключ = записи НЕ видаляться (справжній memory leak)
  • НЕ використовувати рядкові літерали як ключі — вони в String Pool, ніколи не видаляються
  • Не для production caching — використовуйте Caffeine/Guava Cache з LRU + TTL

Часті уточнюючі запитання:

  • Чому System.gc() не гарантує видалення? — це лише рекомендація, JVM може проігнорувати
  • Коли WeakHashMap не видалить записи? — хтось поза Map тримає strong reference на ключ
  • Чи потокобезпечна WeakHashMap? — ні, потрібна Collections.synchronizedMap()
  • Чим WeakReference відрізняється від SoftReference? — Weak видаляється при будь-якому GC, Soft — лише при нестачі пам’яті

Червоні прапорці (НЕ говорити):

  • «WeakHashMap = повноцінний кеш» — ні, немає TTL, LRU, ліміту розміру
  • «GC запуститься одразу після key = null» — ні, System.gc() лише рекомендація
  • «Значення теж слабке посилання» — ні, лише ключ; значення = strong reference

Пов’язані теми:

  • [[12. Чи можна використовувати mutable об’єкт як ключ в HashMap]]
  • [[28. Як правильно підібрати початкову capacity для HashMap]]
  • [[23. Що таке ConcurrentHashMap і чим він відрізняється від HashMap]]