JDK 8 WeakHashMap 详解及详细源码展示

JDK 8 WeakHashMap 详解

WeakHashMap是 Java 中一种用于存储键值对的Map类型。它与常规HashMap的区别在于处理键的方式。在WeakHashMap中,键是被 “弱引用”(WeakReference) 持有。这意味着当键对象在程序的其他地方不再被引用(即它符合垃圾回收条件)时,WeakHashMap中相应的键值对会自动被移除。这种行为基于 Java 中的弱引用概念。弱引用允许对象在没有强引用指向它们时被垃圾回收。WeakHashMap利用这个机制来管理其条目, 这种特性使它非常适合实现缓存或映射需要动态释放资源的场景。以下是其核心原理和源码分析。

一、核心概念

1. 弱引用机制
  • 弱引用(WeakReference):当垃圾回收器扫描到弱引用对象时,无论内存是否充足,都会回收该对象,并将其引用队列(ReferenceQueue)中。
  • WeakHashMap 原理:键对象被包装成弱引用,当键对象被 GC 回收后,对应的键值对会在下一次操作(如 getsize)时被清理。
2. 数据结构
  • 内部结构:与 HashMap 类似,使用数组 + 链表实现哈希表。
  • 键的存储:键被包装为 WeakReference,值被普通引用存储。

二、源码解析

1. 类定义与成员变量
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
    // 默认初始容量
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    // 最大容量
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    
    // 默认负载因子
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    // 存储键值对的数组(每个元素是一个链表头节点)
    private Entry<K,V>[] table;
    
    // 键值对数量
    private int size;
    
    // 扩容阈值(容量 * 负载因子)
    private int threshold;
    
    // 负载因子
    private final float loadFactor;
    
    // 引用队列,用于存储被 GC 回收的键的引用
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    
    // 修改次数,用于快速失败机制
    int modCount;
    
    // 内部 Entry 类,继承自 WeakReference
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;
        
        Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
            super(key, queue); // 将 key 包装为弱引用,并关联引用队列
            this.value = value;
            this.hash = hash;
            this.next = next;
        }
        
        // 其他方法...
    }
}
2. 核心方法:put
public V put(K key, V value) {
    Object k = maskNull(key); // 处理 null 键(转换为内部的 NULL_KEY 对象)
    int h = hash(k); // 计算哈希值
    Entry<K,V>[] tab = getTable(); // 获取当前 table(可能已清理过期条目)
    int i = indexFor(h, tab.length); // 计算数组索引
    
    // 遍历链表查找是否已存在该键
    for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
        if (h == e.hash && eq(k, e.get())) {
            V oldValue = e.value;
            if (value != oldValue)
                e.value = value;
            return oldValue;
        }
    }
    
    modCount++;
    Entry<K,V> e = tab[i];
    tab[i] = new Entry<>(k, value, queue, h, e); // 创建新 Entry 并插入链表头部
    if (++size >= threshold)
        resize(tab.length * 2); // 扩容
    return null;
}
3. 核心方法:get
public V get(Object key) {
    Object k = maskNull(key);
    int h = hash(k);
    Entry<K,V>[] tab = getTable();
    int index = indexFor(h, tab.length);
    Entry<K,V> e = tab[index];
    
    // 遍历链表查找键值对
    while (e != null) {
        if (e.hash == h && eq(k, e.get()))
            return e.value;
        e = e.next;
    }
    return null;
}
4. 清理过期条目
// 获取 table 前先清理已被 GC 的键对应的条目
private Entry<K,V>[] getTable() {
    expungeStaleEntries();
    return table;
}

// 清理已被 GC 的键对应的条目
private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);
            
            // 从链表中移除该条目
            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e)
                        table[i] = next;
                    else
                        prev.next = next;
                    // 帮助 GC
                    e.value = null; // 清除对值的引用
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}
5. 扩容机制
// 扩容方法,与 HashMap 类似
void resize(int newCapacity) {
    Entry<K,V>[] oldTable = getTable();
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    
    Entry<K,V>[] newTable = new Entry[newCapacity];
    boolean oldAltHashing = useAltHashing;
    useAltHashing |= sun.misc.VM.isBooted() &&
            (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    boolean rehash = oldAltHashing ^ useAltHashing;
    
    transfer(oldTable, newTable, rehash);
    table = newTable;
    
    // 调整阈值
    if (size >= threshold / 2) {
        threshold = (int)(newCapacity * loadFactor);
    } else {
        // 清理过程中可能移除了大量条目,重新计算阈值
        expungeStaleEntries();
        transfer(newTable, oldTable, false);
        table = oldTable;
    }
}

三、关键特性分析

1. 弱引用与自动清理
  • 自动回收:当键对象被 GC 回收后,对应的 WeakReference 会被放入引用队列,WeakHashMap 在后续操作中会清理这些条目。
  • 触发时机expungeStaleEntries() 方法在 getTable()size()resize() 等方法中被调用,确保过期条目被及时清理。
2. null 键支持
  • WeakHashMap 允许 null 键,内部将其转换为特殊的 NULL_KEY 对象。
    private static final Object NULL_KEY = new Object();
    
    private static Object maskNull(Object key) {
        return (key == null) ? NULL_KEY : key;
    }
    
3. 线程不安全
  • HashMap 类似,WeakHashMap 非线程安全。多线程环境下需使用 Collections.synchronizedMap 包装。
    Map<K, V> synchronizedMap = Collections.synchronizedMap(new WeakHashMap<>());
    
4. 应用场景
  • 缓存:实现内存敏感的缓存,当内存不足时自动释放资源。
  • 元数据关联:为对象关联临时元数据,当对象被回收时,关联数据自动清除。

四、使用场景

  1. 缓存实现

    // 缓存大对象,当对象无其他强引用时自动释放
    Map<BigObject, Metadata> cache = new WeakHashMap<>();
    
    // 添加缓存
    cache.put(bigObject, new Metadata("info"));
    
    // 使用缓存
    Metadata metadata = cache.get(bigObject);
    
  2. 对象关联数据

    // 为对象关联临时状态,对象被回收时状态自动清除
    Map<MyObject, State> states = new WeakHashMap<>();
    
    public void setObjectState(MyObject obj, State state) {
        states.put(obj, state);
    }
    
  3. 防止内存泄漏

    // 监听器注册(避免持有对象强引用导致内存泄漏)
    Map<Listener, Object> listeners = new WeakHashMap<>();
    
    public void registerListener(Listener listener) {
        listeners.put(listener, new Object());
    }
    

五、注意事项

  1. 值的强引用

    • 值对象通过强引用存储,若值对象持有对键的强引用,会阻止键被 GC 回收。
  2. 性能考虑

    • 频繁 GC 可能导致 WeakHashMap 频繁清理,影响性能。
  3. 遍历行为

    • 迭代过程中可能会发现条目突然消失,因为它们可能在迭代期间被 GC 回收。
  4. 替代方案

    • Java 8 引入的 ConcurrentHashMap + WeakReference 可实现线程安全的弱引用缓存。
    • Guava 库的 CacheBuilder 提供更灵活的缓存实现。

六、总结

JDK 8 的 WeakHashMap 通过弱引用机制实现了键的自动回收,非常适合需要动态释放资源的场景。其核心在于利用 WeakReferenceReferenceQueue 跟踪被 GC 回收的键,并在后续操作中清理对应的条目。理解其弱引用特性、自动清理机制和线程不安全的特点,有助于在实际开发中正确应用 WeakHashMap 解决内存敏感的问题。
WeakHashMap 是 Java 中一个强大且独特的数据结构,通过弱引用管理键值对,在内存敏感的场景如缓存实现、内存管理等方面发挥着重要作用。深入理解其原理、使用方法以及源码实现,有助于我们在编写 Java 程序时更好地优化内存使用,提升程序的性能和稳定性。在实际应用中,遵循最佳实践,合理选择键并处理好多线程并发问题,能让 WeakHashMap 发挥出最大的价值。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值