WeakHashMap详解

1 概述

在 Java 编程中,Map 是一个非常常用的数据结构,其中 HashMap 是最常见的实现之一。然而,在某些高级场景中,我们可能会使用 WeakHashMapWeakHashMapHashMap 在大多数行为上相似,但有一个关键区别:WeakHashMap 不会阻止垃圾回收器(GC)回收其 key 对象。本文将深入探讨 WeakHashMap 是如何实现这一特性的。

2 Java中的引用类型

在开始WeakHashMap之前,我们先要对弱引用有一定的了解。

在 Java 中,引用类型有四种:

  • 强引用(Strong Reference):这是我们正常编码时默认的引用类型。如果一个对象到 GC Roots 有强引用可达,那么这个对象就不会被垃圾回收。
  • 软引用(Soft Reference):软引用阻止 GC 回收的能力相对较弱。如果一个对象只有软引用可达,那么它会在内存不足时被垃圾回收器回收。
  • 弱引用(WeakReference):弱引用无法阻止 GC 回收。如果一个对象只有弱引用可达,那么在下一个 GC 回收执行时,该对象就会被回收掉。
  • 虚引用(Phantom Reference):虚引用非常脆弱,它的唯一作用是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

3 引用队列(Reference Queue)

引用队列是一个重要的概念。一般情况下,当一个对象被标记为垃圾(并不代表已经被回收)后,会加入到引用队列。对于虚引用来说,它指向的对象只有在被回收后才会加入引用队列,因此可以用作记录该引用指向的对象是否已被回收。

4 WeakHashMap 的实现

在Java 8中,WeakHashMapEntry 继承了 WeakReference,并将 key 作为 WeakReference 引用的对象。以下是 WeakHashMapEntry 类的简化实现:

java 复制代码
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);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }

    @SuppressWarnings("unchecked")
    public K getKey() {
        return (K) WeakHashMap.unmaskNull(get());
    }

    public V getValue() {
        return value;
    }

    public V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        K k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            V v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public int hashCode() {
        K k = getKey();
        V v = getValue();
        return Objects.hashCode(k) ^ Objects.hashCode(v);
    }

    public String toString() {
        return getKey() + "=" + getValue();
    }
}

在这个实现中,key 被包装在 WeakReference 中,并且 WeakHashMap 还维护了一个ReferenceQueue,用于记录那些已经被垃圾回收的 key

因此WeakHashMap利用了WeakReference的机制来实现不阻止GC回收Key。

5 如何删除被回收的 key 数据

WeakHashMap 的 Javadoc 中提到,当 key 不再被引用时,其对应的 key/value 也会被移除。那么 WeakHashMap 是如何实现这一点的呢?

通常有两种假设策略:

  1. 当对象被回收时,进行通知:这种方式看似更高效,但 Java 中没有一个可靠的通知回调机制。
  2. WeakHashMap 轮询处理失效的 EntryWeakHashMap 采用的就是这种方式。在 putgetsize 等方法调用时,都会预先调用一个 expungeStaleEntries 方法,来检查并删除失效的 Entry

例如,在 put 方法中:

java 复制代码
public V put(K key, V value) {
    Object k = maskNull(key);
    int h = hash(k);
    Entry<K,V>[] tab = getTable();
    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);
    if (++size >= threshold)
        resize(tab.length * 2);
    return null;
}

put 方法中,WeakHashMap 会调用 getTable 方法,而 getTable 方法会调用 expungeStaleEntries 方法来清理失效的 Entry

java 复制代码
private Entry<K,V>[] getTable() {
    expungeStaleEntries();
    return table;
}

以下是 expungeStaleEntries 方法的实现:

java 复制代码
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;
                    // Must not null out e.next;
                    // stale entries may be in use by a HashIterator
                    e.value = null; // Help GC
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

在这个方法中,WeakHashMap 会遍历 ReferenceQueue,找到那些已经被垃圾回收的 Entry,并从 table 中删除这些 Entry

6 为什么没有使用通知机制

在 Java 中,没有一个可靠的通知回调机制来通知对象被回收。虽然 finalize 方法可以作为辅助验证手段,但它并不是标准的,不同的 JVM 实现可能会有不同的行为,甚至可能不调用 finalize 方法。因此,WeakHashMap 选择了轮询 ReferenceQueue 的方式来处理被回收的 key

7 验证 WeakHashMap 的行为

为了验证 WeakHashMap 的行为,我们可以定义一个简单的 MyObject 类,并重写其 finalize 方法:

java 复制代码
class MyObject {
    private String id;

    public MyObject(String id) {
        this.id = id;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Object(" + id + ") finalize method is called");
        super.finalize();
    }
}

然后编写测试代码:

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

public class WeakHashMapTest {
    private static WeakHashMap<MyObject, Integer> weakHashMap = new WeakHashMap<>();
    private static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            weakHashMap.put(new MyObject(String.valueOf(count)), count);
            count++;
            System.out.println("WeakHashMap size: " + weakHashMap.size());
        }

        // 强制触发 GC
        System.gc();

        // 等待一段时间,确保 GC 完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 再次添加元素
        weakHashMap.put(new MyObject(String.valueOf(count)), count);
        count++;
        System.out.println("WeakHashMap size: " + weakHashMap.size());
    }
}

运行结果如下:

java 复制代码
WeakHashMap size: 1
WeakHashMap size: 2
WeakHashMap size: 3
Object(0) finalize method is called
Object(1) finalize method is called
Object(2) finalize method is called
WeakHashMap size: 1

在这个测试中,我们首先向 WeakHashMap 中添加了三个 Entry,然后强制触发 GC。在 GC 完成后,WeakHashMap 中的 key 被回收,WeakHashMap 的大小减少到 1。

8 总结

WeakHashMap 通过使用 WeakReferenceReferenceQueue 来实现不阻止 GC 回收 key 的功能。它通过轮询 ReferenceQueue 来检测并删除那些已经被垃圾回收的 key,而不是依赖于不可靠的通知机制。这种设计使得 WeakHashMap 在处理那些可能被垃圾回收的 key 时非常有效。

9 思维导图

参考链接

Java WeakHashMap详解(附源码分析)

相关推荐
考虑考虑20 小时前
Jpa使用union all
java·spring boot·后端
用户37215742613520 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊21 小时前
Java学习第22天 - 云原生与容器化
java
渣哥1 天前
原来 Java 里线程安全集合有这么多种
java
间彧1 天前
Spring Boot集成Spring Security完整指南
java
间彧1 天前
Spring Secutiy基本原理及工作流程
java
Java水解1 天前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆1 天前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学1 天前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole1 天前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端