# 问题
10. WeakHashMap 与 HashMap 的区别是什么?适用于哪些场景?
# 标准答案
WeakHashMap 与 HashMap 的主要区别在于:
- WeakHashMap 使用
WeakReference
弱引用存储键,当 GC 发现没有其他强引用指向 key 时,会自动回收对应的 entry; - HashMap 使用强引用存储 key,即使没有外部引用,也不会自动回收,只有
remove()
或扩容时才会删除无用的 entry。
WeakHashMap 适用于缓存、元数据存储等场景,例如 存储短生命周期对象的临时映射,避免内存泄漏。
# 答案解析
# 1. WeakHashMap 的数据结构
WeakHashMap 继承自 AbstractMap
,底层采用 数组 + 链地址法 存储 Entry,与 HashMap 结构类似,但键采用 WeakReference
弱引用包装:
static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
Entry<K,V> next;
}
1
2
3
4
2
3
4
- 键是
WeakReference<Object>
,而值是普通V
类型; - 当 GC 发现 key 只剩
WeakReference
,会清除该对象,并将其放入ReferenceQueue
; WeakHashMap
轮询ReferenceQueue
,发现 key 被 GC 回收后,自动删除该 Entry。
# 2. HashMap 与 WeakHashMap 的区别
对比项 | HashMap | WeakHashMap |
---|---|---|
Key 类型 | 强引用 (StrongReference) | 弱引用 (WeakReference) |
GC 影响 | 不会自动回收 | Key 被 GC 回收后,Entry 自动删除 |
数据结构 | 数组 + 链表/红黑树 | 数组 + 链表 |
适用场景 | 普通数据存储,缓存需手动管理 | 短生命周期对象存储,避免 OOM |
性能 | 较稳定 | 访问时需额外检查 ReferenceQueue ,性能略低 |
# 3. WeakHashMap 的工作机制
核心方法 getTable()
会检查 ReferenceQueue
,清除被 GC 回收的键:
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
Entry<?, ?> e = (Entry<?, ?>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i], p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e) table[i] = next;
else prev.next = next;
break;
}
prev = p;
p = next;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 当 GC 发现 key 没有强引用,会自动清除
WeakReference
并加入ReferenceQueue
; - WeakHashMap 访问时检查
ReferenceQueue
,删除已回收的 entry,避免内存泄漏。
# 4. 适用场景
WeakHashMap 适合存储生命周期较短的对象,典型应用包括:
- 对象缓存(避免内存泄漏):
Map<Object, String> cache = new WeakHashMap<>(); Object key = new Object(); cache.put(key, "Data"); key = null; // 失去强引用 System.gc(); // 触发 GC,WeakHashMap 自动删除 key System.out.println(cache.size()); // 0
1
2
3
4
5
6 - 监听器管理(如 Swing 事件监听):
- 避免监听器对象长期驻留内存,导致 OOM。
- ClassLoader 级别的缓存(如 Spring、Tomcat):
- 防止类加载器持有强引用,导致
OutOfMemoryError
。
- 防止类加载器持有强引用,导致
# 5. WeakHashMap 的常见问题
❌ 常见误区
误认为 WeakHashMap 会立即删除 entry
- 错误示例:
Map<String, String> map = new WeakHashMap<>(); map.put(new String("key"), "value"); System.out.println(map.size()); // 仍然可能是 1
1
2
3 - **解析:**GC 不保证立即清除,只有在 下一次访问时检查
ReferenceQueue
,entry 才会被删除。
- 错误示例:
误以为 WeakHashMap 适用于所有缓存场景
- WeakHashMap 适用于短生命周期对象,但 高频访问的缓存建议使用
ConcurrentHashMap
+SoftReference
,避免数据过快丢失。 - 推荐
Caffeine
、Guava Cache
作为更优选择。
- WeakHashMap 适用于短生命周期对象,但 高频访问的缓存建议使用
误以为 WeakHashMap 线程安全
- 错误示例:
Map<Object, String> map = new WeakHashMap<>(); new Thread(() -> map.put(new Object(), "A")).start(); new Thread(() -> map.put(new Object(), "B")).start();
1
2
3 - **解析:**WeakHashMap 不是线程安全的,并发环境需使用
Collections.synchronizedMap()
包装。
- 错误示例:
# 深入追问
🔹 WeakHashMap 为什么 key 不能是 String 常量?
🔹 为什么 WeakHashMap 需要 ReferenceQueue
机制?
🔹 WeakHashMap 在 remove()
时如何保证数据一致性?
🔹 WeakHashMap 如何优化 GC 触发频率?
# 相关面试题
• WeakReference、SoftReference、PhantomReference 有什么区别?
• WeakHashMap 适用于哪些场景?相比 ConcurrentHashMap 有哪些优势?
• 如何避免缓存过期导致的内存泄漏?
• Caffeine 缓存如何优化 WeakHashMap 的缺点?