# 问题

10. WeakHashMap 与 HashMap 的区别是什么?适用于哪些场景?

# 标准答案

WeakHashMap 与 HashMap 的主要区别在于:

  1. WeakHashMap 使用 WeakReference 弱引用存储键,当 GC 发现没有其他强引用指向 key 时,会自动回收对应的 entry
  2. 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
  • 键是 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
  • 当 GC 发现 key 没有强引用,会自动清除 WeakReference 并加入 ReferenceQueue
  • WeakHashMap 访问时检查 ReferenceQueue,删除已回收的 entry,避免内存泄漏。

# 4. 适用场景

WeakHashMap 适合存储生命周期较短的对象,典型应用包括:

  1. 对象缓存(避免内存泄漏):
    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
  2. 监听器管理(如 Swing 事件监听):
    • 避免监听器对象长期驻留内存,导致 OOM。
  3. ClassLoader 级别的缓存(如 Spring、Tomcat):
    • 防止类加载器持有强引用,导致 OutOfMemoryError

# 5. WeakHashMap 的常见问题

常见误区

  1. 误认为 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 才会被删除。
  2. 误以为 WeakHashMap 适用于所有缓存场景

    • WeakHashMap 适用于短生命周期对象,但 高频访问的缓存建议使用 ConcurrentHashMap + SoftReference,避免数据过快丢失。
    • 推荐 CaffeineGuava Cache 作为更优选择。
  3. 误以为 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 的缺点?