WeakHashMap 学习

一、什么是 WeakHashMap?

WeakHashMap<K, V> 是 Java 标准库(java.util 包)中的一种特殊 Map 实现。它的核心特性是:

键(Key)被包装为弱引用(WeakReference) ,当某个键对象 不再被任何强引用持有 时,即使它还在 WeakHashMap 中,也会在下一次垃圾回收(GC)时被自动移除。

这使得 WeakHashMap 成为一种 自动清理的 Map,非常适合用于缓存或元数据映射等场景。

二、WeakHashMap 的基本使用

示例代码:观察自动清理行为

java 复制代码
import java.util.WeakHashMap;

public class WeakHashMapDemo {
    public static void main(String[] args) {
        WeakHashMap<Key, String> map = new WeakHashMap<>();

        Key key = new Key("obj1");
        map.put(key, "value1");

        System.out.println("Before GC: " + map.size()); // 输出 1

        key = null; // 移除唯一强引用

        // 建议触发 GC(不保证立即执行)
        System.gc();

        try { Thread.sleep(100); } catch (InterruptedException e) {}

        // 调用 size() 会触发 expungeStaleEntries()
        System.out.println("After GC: " + map.size()); // 通常输出 0
    }

    static class Key {
        private final String name;
        public Key(String name) { this.name = name; }
        @Override
        public String toString() { return name; }
    }
}

⚠️ 注意:System.gc() 只是建议 JVM 执行 GC,实际是否回收取决于 JVM 实现和运行时状态。但只要 GC 发生,且 key 无强引用,条目就会被清除。

三、适用场景(Where to Use)

✅ 推荐场景:

  1. 对象元数据缓存(Metadata Cache)

    例如:为每个对象关联一个调试信息、监听器列表、临时配置等,但不希望这些元数据阻止对象被回收。

  2. 避免内存泄漏的监听器注册表

    在事件驱动系统中,若监听器未显式注销,可能造成内存泄漏。使用 WeakHashMap<Listener, ?> 可让监听器在无其他引用时自动移除。

  3. ClassLoader 或 Class 相关的缓存

    比如动态代理类、反射元数据缓存等,避免因缓存导致 ClassLoader 无法卸载。

❌ 不适合场景:

  • 需要长期保留数据的缓存(如用户会话、热点数据)→ 应使用 SoftReference(软引用)或成熟缓存框架(如 Caffeine、Guava Cache)。
  • 键是基本类型包装类(如 Integer, String)且来自常量池 → 因为它们可能被 JVM 强引用(如字符串字面量),导致无法回收。

四、WeakHashMap 的优缺点

优点 缺点
✅ 自动清理无用条目,防止内存泄漏 ❌ 条目可能随时消失,不适合需要稳定存储的场景
✅ 与 GC 协同工作,无需手动管理 ❌ 清理时机不可控(依赖 GC)
✅ 内存友好,适合辅助性缓存 ❌ 性能略低于 HashMap(每次操作需清理 stale entries)
✅ 线程不安全(与 HashMap 一致) ❌ 不支持 null key(因为 WeakReference(null) 无意义)

补充:WeakHashMap 不允许 null 作为 key ,但允许 null 作为 value。


五、底层原理(How It Works)

1. 弱引用(WeakReference)

WeakHashMap 内部使用 WeakReference 包装每个 key:

java 复制代码
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    // ...
}
  • 当 key 对象仅被 WeakReference 引用时,JVM 认为其"可回收"。
  • GC 时,JVM 会将该 WeakReference 加入其关联的 ReferenceQueue

2. ReferenceQueue 机制

WeakHashMap 构造时会创建一个 ReferenceQueue

java 复制代码
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
  • 当 key 被 GC 回收后,对应的 Entry 会被放入此队列。
  • WeakHashMap每次访问(get/put/size 等)时 ,会调用 expungeStaleEntries() 方法,遍历队列并删除已失效的条目。

这就是为什么 map.size() 在 GC 后返回 0 ------ 因为 size() 触发了清理。

3. 清理不是实时的!

⚠️ 关键点WeakHashMap 不会在 GC 后立即清理,而是在下次调用其方法时才清理。因此:

  • 如果长时间不访问 map,stale entries 会堆积(但不占用 key 对象内存,只占 Entry 对象内存)。
  • 它不是"实时缓存",而是"懒清理"。

六、与 JVM 的关系

WeakHashMap 的行为高度依赖 JVM 的 垃圾回收机制引用类型语义

引用类型 是否阻止 GC WeakHashMap 使用
强引用(Strong) ✅ 是 不使用
软引用(Soft) ✅(直到内存不足) SoftReference,用于内存敏感缓存
弱引用(Weak) ❌ 否 WeakHashMap 的 key
虚引用(Phantom) ❌ 否 用于跟踪对象回收(不用于 Map)

因此,WeakHashMap 的"自动清理"能力完全由 JVM 的 GC 决定,属于 JVM 与 Java 语言协同设计的典型范例

七、生产环境是否使用?如何使用?

生产环境可以使用,但需谨慎!

正确使用姿势:
  1. 明确目的:仅用于"辅助性、可丢失"的元数据映射。
  2. 避免依赖清理时机:不要假设条目何时消失。
  3. 不要用于主业务数据存储
  4. 注意 key 的生命周期:确保 key 确实会变成"仅被 WeakHashMap 引用"。
生产级示例:监听器注册表(防内存泄漏)
java 复制代码
import java.util.*;

public class EventManager {
    // 使用 WeakHashMap 避免监听器泄漏
    private final Map<Listener, Boolean> listeners = new WeakHashMap<>();

    public void addListener(Listener listener) {
        listeners.put(listener, Boolean.TRUE);
    }

    public void fireEvent(String event) {
        // 清理 stale entries 并通知有效监听器
        for (Listener l : new ArrayList<>(listeners.keySet())) {
            if (l != null) {
                l.onEvent(event);
            }
        }
    }

    interface Listener {
        void onEvent(String event);
    }
}

即使调用方忘记 removeListener,只要监听器对象无其他强引用,就会被自动清理。

八、替代方案对比

方案 特点 适用场景
WeakHashMap key 弱引用,GC 后自动移除 元数据、监听器、临时关联
SoftReference + Map key 软引用,内存不足时才回收 内存敏感缓存(如图片缓存)
Guava Cache / Caffeine 支持 LRU、TTL、软/弱引用等 通用缓存,功能强大
IdentityHashMap 基于 == 比较 特殊场景(如防止重写 equals 的干扰)

一般建议:除非明确需要弱引用语义,否则优先使用成熟缓存框架

九、总结

维度 说明
本质 key 为弱引用的 Map
核心机制 依赖 JVM GC + ReferenceQueue
自动清理 在下次访问时触发(懒清理)
线程安全 ❌ 不安全(需外部同步)
生产可用 ✅ 但仅限特定场景(元数据、防泄漏)
与 JVM 关系 深度依赖 GC 行为和引用语义

💡 一句话口诀

"WeakHashMap 存的是'影子',主身一走,影子就散。"

相关推荐
一 乐4 小时前
心理健康管理|基于springboot + vue心理健康管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
jiayong234 小时前
Spring XML解析与BeanDefinition注册详解
xml·java·spring
木井巳6 小时前
【多线程】并发安全
java·java-ee
It's now12 小时前
Spring AI 基础开发流程
java·人工智能·后端·spring
cxh_陈12 小时前
线程的状态,以及和锁有什么关系
java·线程·线程的状态·线程和锁
计算机毕设VX:Fegn089512 小时前
计算机毕业设计|基于springboot + vue图书商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
R.lin12 小时前
Java 8日期时间API完全指南
java·开发语言·python
毕设源码-赖学姐12 小时前
【开题答辩全过程】以 高校教学质量监控平台为例,包含答辩的问题和答案
java·eclipse
高山上有一只小老虎13 小时前
翻之矩阵中的行
java·算法