Skip to content

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 映射
WeakHashMapGC 时自动回收缓存、自动清理
手动清理需要调用 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。


相关链接

基于 VitePress 构建