WeakHashMap 将引用放入 ReferenceQueue 后如何实际找到条目

WeakHashMap how is the entry _actually_ found after the reference is put on the ReferenceQueue

A WeakHashMap 的工作原理与 WeakReferenceReferenceQueue 的结合非常相似 - 关于此的新闻为零。这是它应该如何工作的精简示例:

public class ReferenceQueuePlayground {

    public static void main(String[] args) {
        ReferenceQueue<Referent> q = new ReferenceQueue<>();

        Referent ref = new Referent();
        WeakReference<Referent> weak = new WeakReference<>(ref, q);

        ref = null;

        // wait for GC to reclaim Referent
        while (weak.get() != null) {
            System.gc();
        }

        // this might return null, because ReferenceQueue is notified asynchronously
        // but I am assuming the happy path here 
        Reference<? extends Referent> reference = q.poll();

        // this will be false
        System.out.println(reference == null);
        // this will be true
        System.out.println(reference.get() == null);
    }

    @RequiredArgsConstructor
    @Getter
    static class Referent {

    }
}

这正是 WeakHashMap 的工作方式 - 当 referent 被回收并且 reference 被放置在 ReferenceQueue 上时它会收到通知。在后续的一些操作中,expungeStaleEntries 被调用,它基本上会从这个 ReferenceQueue 中一个一个地取出元素并对其进行操作。

我遇到的问题:如果 referent 现在消失了,怎么会 "act on them" 呢?毕竟这是一个 ...HASHMap,所以为了删除该元素,它必须知道它是 hashCode。你怎么知道现在已经消失的东西的 hashCode

有两种方法。第一个是 线性搜索

由于 referent 确实没有了,你无法计算它上面的 hashCode,你可以用 [=15] 搜索 Reference =],跨越 Map 中的 all 个条目。在发布的示例中,您可以添加几行:

    WeakReference<Referent> weak = new WeakReference<>(ref, q);
    // <--- this
    System.out.println(weak);

    ref = null;

    while (weak.get() != null) {
        System.out.println("not yet");
        System.gc();
    }

    Reference<? extends Referent> reference = q.poll();
    // <---- and this
    System.out.println(reference);

这两个将打印相同的内容,这是完全有道理的。所以理论上,一个 WeakHashMap 可以取它得到的 reference(实际上是一个 Entry)并遍历它的内部数组,直到找到匹配项。

显然,这会很慢。

第二种方法是WeakHashMap实际上采用的方法。首次创建 Entry 时,它会计算 hashCode 并将其放入本地字段:

/**
  * Creates new entry.
  */
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;
}

此时它知道 Key,因此它可以计算 hashCode。稍后调用 expungeStaleEntries 时:

 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);

它已经知道 hashCode,因为它是在此之前计算的。它不知道 Key,但也不需要它。

这将有助于找到这个条目所在的桶,但要真正找到特定的条目,它只能在条目本身上使用 ==。由于 Key 消失了,equals 是不可能的,但这并不重要。