Вопрос 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. Можно ли использовать изменяемый объект как ключ в HashMap]]
  • [[28. Как правильно выбрать начальный capacity для HashMap]]
  • [[23. Что такое ConcurrentHashMap и чем он отличается от HashMap]]