WeakHashMap:被 GC 牵着走的 Map
WeakHashMap 是什么
WeakHashMap 的 key 是弱引用(WeakReference),当 key 不再被外部强引用时,下一次 GC 会自动回收它,entry 也自动从 Map 中移除。
这有什么用?实现自动清理的缓存——当对象不再被使用时,不需要手动调用 remove(),GC 会帮你清理。
java
WeakHashMap<String, Object> cache = new WeakHashMap<>();
String key = new String("api-result");
cache.put(key, someData);
// key 不再被外部引用时,下一次 GC 会自动删除这条记录强引用 vs 弱引用
理解 WeakHashMap 之前,先理解 Java 的四种引用:
强引用(Strong Reference):
String s = new String("hello");
→ 只要 s 还活着,hello 就不会被 GC 回收
软引用(SoftReference):
SoftReference<String> ref = new SoftReference<>(new String("hello"));
→ 内存不足时才会被 GC 回收
弱引用(WeakReference):
WeakReference<String> ref = new WeakReference<>(new String("hello"));
→ 下一次 GC 必定被回收
虚引用(PhantomReference):
→ 完全不影响 GC,只是用于跟踪对象被回收的时机WeakHashMap 的工作原理
java
// WeakHashMap 的内部结构
public class WeakHashMap<K, V> {
// key 使用 WeakReference 包装
private final ReferenceQueue<K> queue = new ReferenceQueue<>();
private Entry<K, V>[] table;
private static class Entry<K, V> extends WeakReference<K> {
V value;
Entry<K, V> next;
// ...
}
}当 key 的强引用消失后,WeakReference 被加入 ReferenceQueue。WeakHashMap 在每次操作前会清理这些「已死」的 entry。
演示:WeakHashMap 的自动清理
java
import java.util.*;
import java.lang.ref.*;
public class WeakHashMapDemo {
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, Integer> map = new WeakHashMap<>();
// 强引用 key
String strongKey = new String("strong");
map.put(strongKey, 1);
// 弱引用 key(模拟缓存)
String weakKey = new String("weak");
map.put(weakKey, 2);
System.out.println("GC 前: " + map);
System.out.println("strongKey = " + strongKey);
System.out.println("weakKey = " + weakKey);
// 移除强引用
strongKey = null;
weakKey = null;
// 主动触发 GC
System.gc();
Thread.sleep(100);
System.out.println("\nGC 后: " + map);
// strongKey 和 weakKey 都被回收了
}
}典型输出:
GC 前: {strong=1, weak=2}
strongKey = strong
weakKey = weak
GC 后: {} ← 两个 key 都被回收了常见陷阱
陷阱 1:String 字面量不会被回收
String 字面量在常量池中有强引用,所以不会从 WeakHashMap 中被回收:
java
WeakHashMap<String, Object> map = new WeakHashMap<>();
// ❌ 字面量不会被回收(常量池强引用)
map.put("constant-key", someData);
map.put(new String("another-key"), someData); // 这个可以回收
System.gc();
System.out.println(map); // "constant-key" 仍然存在!解决方案:使用 new String() 创建 key,而不是字面量:
java
// ✅ 都可以被回收
map.put(new String("key1"), value1);
map.put(new String("key2"), value2);陷阱 2:value 持有 key 的强引用
如果 value 持有 key 的强引用,key 就永远不会被回收:
java
WeakHashMap<String, List<String>> map = new WeakHashMap<>();
// ❌ value 持有 key 的强引用!key 永远不会被回收
List<String> list = new ArrayList<>();
list.add("some data");
map.put(new String("key"), list); // list 持有对 key 的引用?
// ✅ 正确做法:不要让 value 持有 key 的引用
map.put(new String("key"), new ArrayList<>());陷阱 3:误以为 WeakHashMap 适合所有缓存
WeakHashMap 的回收时机是「下一次 GC」,不是「对象不使用时立即回收」。如果 GC 不发生,对象仍然占用内存。
典型应用场景
场景 1:缓存外部资源
java
// 缓存图片数据(外部资源)
WeakHashMap<ImageKey, SoftReference<Bitmap>> cache = new WeakHashMap<>();
public Bitmap getImage(ImageKey key) {
SoftReference<Bitmap> ref = cache.get(key);
if (ref != null) {
Bitmap cached = ref.get();
if (cached != null) return cached;
}
Bitmap loaded = loadBitmap(key);
cache.put(key, new SoftReference<>(loaded));
return loaded;
}场景 2:缓存计算结果
java
// 缓存解析结果
WeakHashMap<String, ParseResult> cache = new WeakHashMap<>();
public ParseResult parse(String xml) {
return cache.computeIfAbsent(
new String(xml), // 创建新 String,避免常量池强引用
k -> doParse(k)
);
}场景 3:替代 Map 作为弱键容器
java
// 注册表模式:用 WeakHashMap 让对象自动注销
WeakHashMap<Service, ServiceInfo> registry = new WeakHashMap<>();
public void register(Service service) {
registry.put(service, new ServiceInfo(service));
}
// 当 service 不再被外部引用时,自动从 registry 中移除WeakHashMap vs 其他 Map
| Map 类型 | key 回收 | 适用场景 |
|---|---|---|
| HashMap | 永不自动回收 | 普通 key-value 映射 |
| WeakHashMap | GC 时自动回收 | 缓存、自动清理 |
| 手动清理 | 需要调用 remove() | 需要精确控制的场景 |
WeakReference 的手动使用
除了 WeakHashMap,也可以直接使用 WeakReference:
java
import java.lang.ref.*;
WeakReference<String> ref = new WeakReference<>(new String("hello"));
// 使用
String s = ref.get();
if (s != null) {
// 对象还活着
} else {
// 对象已被 GC 回收
}
// 检查引用是否在引用队列中
ReferenceQueue<String> queue = new ReferenceQueue<>();
WeakReference<String> queued = new WeakReference<>(new String("world"), queue);
// 阻塞等待或轮询
Reference<? extends String> r = queue.remove();
if (r != null) {
System.out.println("被回收了: " + r);
}总结
| 要点 | 说明 |
|---|---|
| key 类型 | 弱引用,GC 时自动回收 |
| value 类型 | 强引用,不会自动回收 |
| 回收时机 | 下一次 GC 时 |
| 常见陷阱 | String 字面量有强引用、value 持有 key 引用 |
| 适用场景 | 缓存、自动清理的注册表 |
一句话:WeakHashMap 让 GC 帮你做「清理工」——但要小心别让别的东西「收养」了你的 key。
