Что такое WeakHashMap?
WeakHashMap хранит ключи как слабые ссылки (WeakReference): 4. "Мёртвые" записи удаляются из таблицы
🟢 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):
- GC обнаруживает, что на ключ осталась только слабая ссылка
- Помещает ссылку в
ReferenceQueue - При следующей операции над WeakHashMap вызывается
expungeStaleEntries() - “Мёртвые” записи удаляются из таблицы
Когда 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.
Сценарии использования
- Кэширование метаданных — привязка данных к объектам из чужого кода
- Lapsed Listener — хранение слушателей без предотвращения GC
- Thread-local данные — альтернатива ThreadLocal
Чего WeakHashMap НЕ делает
| Что | WeakHashMap | Настоящий Cache (Caffeine) |
|---|---|---|
| Автоочистка по GC | Да | Нет |
| LRU/LFU eviction | Нет | Да |
| TTL (время жизни) | Нет | Да |
| Размер кеша | Не ограничен | Ограничен |
| Потокобезопасность | Нет | Да |
Типичные ошибки
- Использование строковых литералов как ключей — они в String Pool, никогда не удаляются
- Ожидание мгновенной очистки — очистка ленивая, только при обращении
- Забытый 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
- Integer Cache (-128 to 127): Integer-ключи из кеша никогда не удаляются
- String Pool: Строковые литералы — сильные ссылки от JVM
- 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]]