【Java】WeakHashMap详解:原理、实现与实战
大家好,我是云扬~ 今天想和大家深入聊聊 Java 集合框架中一个特殊的存在 ——WeakHashMap。它和我们常用的 HashMap 长得很像,但骨子里藏着 “自动回收” 的黑科技,在缓存设计、临时数据存储等场景中特别实用。这篇文章会从原理到代码,带大家彻底搞懂 WeakHashMap!
一、先搞懂:WeakHashMap 和 HashMap 的核心区别
我们平时用的 HashMap,key 是强引用—— 只要 HashMap 本身不被回收,key 和对应的 value 就会一直占着内存。而 WeakHashMap 的关键特性是:key 是弱引用,不会阻止垃圾回收(GC)。
简单说:当 WeakHashMap 的 key 不再被其他强引用指向时,下一次 GC 就会回收这个 key,而 WeakHashMap 会自动删除对应的 key-value 对,避免内存泄漏。
二、Java 四种引用类型(基础必备)
要理解 WeakHashMap,必须先清楚 Java 的四种引用类型,这是它的核心底层支撑:
| 引用类型 | 特点 | 适用场景 |
|---|---|---|
| 强引用(默认) | 只要可达,绝不回收 | 普通对象存储(如 HashMap 的 key) |
| 软引用(SoftRef) | 内存不足时才回收 | 缓存(如图片缓存) |
| 弱引用(WeakRef) | 下一次 GC 必定回收 | WeakHashMap 的 key |
| 虚引用(PhantomRef) | 无法获取对象,仅用于监听回收事件 | 资源释放监听 |
WeakHashMap 正是利用了弱引用的特性,让 key 可以被自动回收。
三、WeakHashMap 的底层实现原理
1. Entry 继承 WeakReference
WeakHashMap 的内部类 Entry 并没有直接持有 key 的强引用,而是继承了 WeakReference,将 key 作为弱引用的目标对象:
// WeakHashMap 内部 Entry 源码简化
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
// key 被包装成弱引用,传入引用队列
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue); // 调用 WeakReference 构造器,key 成为弱引用
this.value = value;
this.hash = hash;
this.next = next;
}
// ... 其他方法
}
这里的关键是 super(key, queue):将 key 交给 WeakReference 管理,同时关联一个引用队列(ReferenceQueue)。
2. 引用队列的作用
当 key 被 GC 标记为垃圾后,会被加入到这个引用队列中。WeakHashMap 在执行 put()、get()、size() 等方法时,会先检查引用队列,把已经失效的 Entry(key 已被回收)从哈希表中删除 —— 这就是 “自动清理” 的核心。
四、代码实战:验证 WeakHashMap 的特性
光说不练假把式,我们用代码验证 WeakHashMap 的 key 回收机制:
示例 1:key 无强引用时自动回收
import java.util.WeakHashMap;
public class WeakHashMapTest {
public static void main(String[] args) {
// 1. 创建 WeakHashMap
WeakHashMap<String, String> weakMap = new WeakHashMap<>();
// 2. key 是临时对象,无其他强引用
String key = new String("云扬的key");
weakMap.put(key, "WeakHashMap 测试值");
// 3. 打印此时的 map 内容(有值)
System.out.println("GC前:" + weakMap); // 输出:{云扬的key=WeakHashMap 测试值}
// 4. 移除 key 的强引用(关键!)
key = null;
// 5. 手动触发 GC
System.gc();
// 暂停一下,让 GC 完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 6. 再次打印 map(key 已被回收,Entry 被自动删除)
System.out.println("GC后:" + weakMap); // 输出:{}
}
}
运行结果:
GC前:{云扬的key=WeakHashMap 测试值}
GC后:{}
结论:当 key 没有强引用时,GC 会回收 key,WeakHashMap 自动删除对应的 Entry。
示例 2:key 有强引用时不回收
如果 key 还有其他强引用,即使放入 WeakHashMap,也不会被回收:
public class WeakHashMapStrongRefTest {
public static void main(String[] args) {
WeakHashMap<String, String> weakMap = new WeakHashMap<>();
// 强引用持有 key
String strongKey = new String("强引用key");
weakMap.put(strongKey, "不会被回收的值");
// 触发 GC
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// key 有强引用,不会被回收
System.out.println("有强引用时:" + weakMap); // 输出:{强引用key=不会被回收的值}
// 移除强引用
strongKey = null;
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 此时 key 被回收
System.out.println("移除强引用后:" + weakMap); // 输出:{}
}
}
五、WeakHashMap 如何清理失效 Entry?
很多人会疑惑:key 被 GC 回收后,WeakHashMap 是怎么知道要删除对应的 Entry 的?
答案是:主动轮询 + 引用队列。WeakHashMap 并没有依赖 Java 的 “对象回收通知”(因为 Java 没有可靠的回调机制),而是在每次调用 put()、get()、size()、isEmpty() 等方法时,都会先执行 expungeStaleEntries() 方法,遍历引用队列,删除所有失效的 Entry:
// WeakHashMap 核心清理方法(简化版)
private void expungeStaleEntries() {
Entry e;
// 遍历引用队列,取出所有已回收 key 对应的 Entry
while ((e = (Entry.poll()) != null) {
int hash = e.hash;
int index = indexFor(hash, table.length);
// 从哈希表中删除该 Entry(链表操作)
Entry> prev = table[index];
Entry p = prev;
while (p != null) {
Entry> next = p.next;
if (p == e) {
if (prev == e)
table[index] = next;
else
prev.next = next;
e.value = null; // 释放 value 的引用,帮助 GC
size--;
break;
}
prev = p;
p = next;
}
}
}
这就是为什么我们在示例中触发 GC 后,WeakHashMap 会自动清空 —— 下次调用 toString()(间接调用 size())时,会触发清理逻辑。
六、使用场景与注意事项
1. 适合场景
- 临时缓存:不需要手动清理,key 不再使用时自动回收(如缓存用户会话,会话过期后自动删除)。
- 避免内存泄漏:当 key 是外部对象,且可能被回收时,用 WeakHashMap 不会阻止 GC(如 HashMap 若不手动删除 key,可能导致内存泄漏)。
2. 注意事项
- value 可能内存泄漏:虽然 key 会被回收,但 value 仍可能被 Entry 持有强引用。如果 value 很大,建议在 key 回收后手动清理 value(可通过重写
finalize()或使用引用队列监听)。 - 线程不安全:和 HashMap 一样,WeakHashMap 不是线程安全的,多线程环境下需使用
Collections.synchronizedMap()包装。 - key 不能为 null:WeakHashMap 的 key 是弱引用,null 没有引用,会被直接回收,因此不允许 key 为 null(HashMap 允许 key 为 null)。
七、总结
WeakHashMap 的核心是弱引用 + 引用队列:
- key 被包装成弱引用,不阻止 GC。
- key 被回收后,会加入引用队列。
- WeakHashMap 操作时(put/get/size),主动清理队列中的失效 Entry。
它是 Java 中解决 “自动回收临时数据” 的利器,尤其适合缓存场景。希望这篇文章能帮大家彻底搞懂 WeakHashMap,下次遇到类似需求时能灵活运用~
如果有疑问或补充,欢迎在评论区交流!



