本文目录导读:

WeakHashMap弱键自动回收机制
WeakHashMap 是 Java 中的一种特殊 Map 实现,其键使用弱引用(WeakReference)包装,当键对象不再被外部强引用持有时,垃圾回收器会自动回收该键,并自动删除对应的条目。
核心原理
// 简化的内部实现
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
}
自动回收触发条件
键对象失去强引用
public class WeakHashMapDemo {
public static void main(String[] args) {
WeakHashMap<KeyObject, String> map = new WeakHashMap<>();
// 创建强引用
KeyObject key = new KeyObject("key1");
map.put(key, "value1");
// key仍然有强引用,不会回收
System.out.println(map.size()); // 1
key = null; // 移除强引用
// 此时只有 WeakHashMap 中的弱引用持有该键
// GC 后会被回收
System.gc();
System.out.println(map.size()); // 0(可能为0)
}
}
GC 执行时自动清理
当垃圾回收器运行时,会通过 ReferenceQueue 机制检测弱引用是否被回收:
// WeakHashMap 内部的 ReferenceQueue
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
// 清理方法,在每次操作时调用
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
// 从哈希表中移除该条目
// ...
}
}
}
实际应用场景
缓存系统
public class ImageCache {
private WeakHashMap<ImageKey, BufferedImage> cache = new WeakHashMap<>();
public BufferedImage getImage(ImageKey key) {
BufferedImage img = cache.get(key);
if (img == null) {
img = loadFromDisk(key);
cache.put(key, img);
}
return img;
}
}
元数据存储
public class MetadataManager {
private WeakHashMap<Object, Metadata> metadataMap = new WeakHashMap<>();
public void setMetadata(Object obj, Metadata meta) {
metadataMap.put(obj, meta);
}
public Metadata getMetadata(Object obj) {
return metadataMap.get(obj);
}
// 当对象不再使用时,元数据自动清理
}
监听器/回调注册
public class EventManager {
private WeakHashMap<Listener, Void> listeners = new WeakHashMap<>();
public void register(Listener listener) {
listeners.put(listener, null);
}
// 监听器对象不再使用时自动移除,避免内存泄漏
}
关键注意事项
值对象不会自动回收
WeakHashMap<Key, HeavyObject> map = new WeakHashMap<>(); map.put(key, new HeavyObject()); // 即使key被回收,value不会被回收! // 解决办法:值也使用弱引用包装 WeakHashMap<Key, WeakReference<HeavyObject>> safeMap = new WeakHashMap<>();
String 作为键的特殊情况
// String.intern() 返回的字符串有强引用
String s1 = "abc"; // 字符串常量池中的强引用
String s2 = new String("abc"); // 堆上的对象
map.put(s2, "value"); // s2可以被回收
map.put(s1, "value"); // 不可被回收
自动回收的时机不确定
// 不要依赖立即回收 map.put(key, "value"); key = null; System.gc(); // 此时可能尚未回收 Thread.sleep(100); // 建议调用 size() 等方法触发清理 map.size(); // 内部会调用 expungeStaleEntries()
与普通 HashMap 对比
| 特性 | WeakHashMap | HashMap |
|---|---|---|
| 键引用类型 | 弱引用 | 强引用 |
| 自动回收 | 支持 | 不支持 |
| 内存泄漏风险 | 低 | 高(需手动移除) |
| 使用场景 | 缓存、元数据 | 大多数场景 |
最佳实践
- 键必须是长生命周期的对象,否则很快会被回收
- 避免在并发场景下使用(非线程安全)
- 不适合存储短暂使用的键值对
- 结合 ReferenceQueue 使用可以监听回收事件
// 监听回收事件示例
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakHashMap<Object, String> map = new WeakHashMap<>();
// 可以另起线程监控回收
new Thread(() -> {
try {
while (true) {
Reference<? extends Object> ref = queue.remove();
System.out.println("键被回收: " + ref);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
WeakHashMap 的弱键自动回收机制在需要自动清理不用的条目时非常有用,特别适合缓存、元数据管理、监听器注册等场景,但需要理解其工作原理和限制,避免误用导致的问题。