美文网首页
聊聊Java WeakHashMap背后的事情

聊聊Java WeakHashMap背后的事情

作者: LittleMagic | 来源:发表于2020-08-19 21:50 被阅读0次

前言

本月还没有写过Java相关的东西,今天终于挤出点时间来了,弄一篇基础知识吧。

WeakHashMap是平时常见的HashMap的变种,它是基于弱引用(WeakReference)的。我们已经知道,不管内存是否足够,弱引用对象都会随着GC被回收,所以WeakHashMap特别适用于内存敏感的局部缓存场景。本文简单探究一下它的部分实现细节。

属性

WeakHashMap的属性比HashMap来得简单,列举如下。

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {
    /**
     * The default initial capacity -- MUST be a power of two.
     */
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    /**
     * The load factor used when none specified in constructor.
     */
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    Entry<K,V>[] table;
    /**
     * The number of key-value mappings contained in this weak hash map.
     */
    private int size;
    /**
     * The next size value at which to resize (capacity * load factor).
     */
    private int threshold;
    /**
     * The load factor for the hash table.
     */
    private final float loadFactor;
    /**
     * Reference queue for cleared WeakEntries
     */
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    // ......

WeakHashMap的初始容量、最大容量和默认装载因子与HashMap相同,但是没有转化成红黑树的阈值(TREEIFY_THRESHOLD、MIN_TREEIFY_CAPACITY),说明WeakHashMap的哈希桶数组table是纯拉链的。另外一个不同点是WeakHashMap需要维护一个引用队列,弱引用会注册到这个引用队列,它的作用下面会看到。

Entry(键值对)结构

/**
 * The entries in this hash table extend WeakReference, using its main ref
 * field as the key.
 */
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    final int hash;
    Entry<K,V> next;

    /**
     * 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;
    }

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

    public V getValue() {
        return value;
    }

    // ......
}

Entry内部类直接继承自WeakReference,在构造Entry时也会先调用WeakReference的构造方法WeakReference(T referent, ReferenceQueue<? super T> q),传入被引用对象(Entry的key)和注册到的引用队列(queue),所以“弱Entry”的本质其实是“弱key”——即当key的可达性发生变化时(变为弱可达或者不可达),GC的同时会自动将key关联的Entry放入队列。

弱Entry的清除

WeakHashMap上的读写等操作就是HashMap对应逻辑的简化版,不再赘述,仅来看看弱Entry是如何被清除掉的。

如上图所示,对WeakHashMap的几乎所有操作都会调用getTable()这个方法来取得哈希桶数组。但是在返回table之前,要先调用expungeStaleEntries()方法:

private Entry<K,V>[] getTable() {
    expungeStaleEntries();
    return table;
}

/**
 * Expunges stale entries from the table.
 */
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;
            }
        }
    }
}

expungeStaleEntries()方法会加锁遍历引用队列,取出其中已经被回收掉key的Entry,调用indexFor()方法根据其哈希值定位到哈希桶数组中的桶,再遍历桶对应的单链表,删除对应的Entry(注意区分Entry是否在链表头的情况)。

可见,所谓“删除”只是将Entry的value置为空,因为key已经被回收掉了,这样做就能保证整个Entry在下一次GC时被彻底清理。通过在增删改查之前调用expungeStaleEntries()方法,GC的效果就可以及时地反映到table上了。

一个小问题

看官可能会有些疑惑:构造Entry时使用的弱引用referent明明是key,为什么从引用队列poll时,出来的却是Entry<K,V>呢?不妨再看一眼WeakReference的基类Reference中的enqueue()方法:

/**
 * Adds this reference object to the queue with which it is registered,
 * if any.
 *
 * <p> This method is invoked only by Java code; when the garbage collector
 * enqueues references it does so directly, without invoking this method.
 *
 * @return   <code>true</code> if this reference object was successfully
 *           enqueued; <code>false</code> if it was already enqueued or if
 *           it was not registered with a queue when it was created
 */
public boolean enqueue() {
    return this.queue.enqueue(this);
}

// 这是ReferenceQueue.enqueue()方法
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
    synchronized (lock) {
        // Check that since getting the lock this reference hasn't already been
        // enqueued (and even then removed)
        ReferenceQueue<?> queue = r.queue;
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;
        r.queue = ENQUEUED;
        r.next = (head == null) ? r : head;
        head = r;
        queueLength++;
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        lock.notifyAll();
        return true;
    }
}

从注释可知,当垃圾收集器将引用插入引用队列时,虽然不会真正调用上述enqueue()方法,但是逻辑是相同的,都是插入this。也就是说,实际进入引用队列的要么是WeakReference本身,要么是继承了WeakReference的对象实例——在WeakHashMap中自然就是Entry了。

测试一下

public class WeakHashMapExample {
  public static void main(String[] args) throws Exception {
    WeakHashMap<String, Integer> map = new WeakHashMap<>();

    map.put("first", 1);
    map.put(new String("second"), 2);
    String third = new String("third");
    map.put(third, 3);

    System.gc();

    System.out.println(map.get("first"));  // 1
    System.out.println(map.get("second")); // null
    System.out.println(map.get("third"));  // 3
  }
}

注意到只有"second"会消失。"first"位于字符串常量池中,"third"保持有强引用,所以手动触发GC不会将它们回收掉。

The End

还有事情要做,民那晚安晚安。

相关文章

  • 聊聊Java WeakHashMap背后的事情

    前言 本月还没有写过Java相关的东西,今天终于挤出点时间来了,弄一篇基础知识吧。 WeakHashMap是平时常...

  • Java WeakHashMap

    作为一个java开发者肯定都知道且使用HashMap,但估计大部分人都不太知道WeakHashMap。从类定义上来...

  • RefenceQueue的源码解析,以及WeakHashMap的

    Java RefenceQueue WeakHashMap 首先介绍Java中的四种引用: 强引用:如Object...

  • WeakHashMap源码分析

    WeakHashMap WeakHashMap介绍 WeakHashMap继承于AbstractMap,实现了Ma...

  • java源码-WeakHashMap

    开篇  作为Map系列的最后一篇,我觉得有必要讲讲WeakHashMap这个类,因为这个类可以解决一些oom的问题...

  • Java集合——WeakHashMap

    WeakHashMap是一个基于Map接口实现的散列表,实现细节与HashMap类似(都有负载因子、散列函数等等,...

  • WeakReference 在 WeakHashMap 和 T

    WeakHashMap TheadLocalMap WeakHashMap 和 ThreadLocalMap 中 ...

  • WeakHashMap 指南

    WeakHashMap 指南 1. 概览 在这篇文章中,我们将会讨论 java.util包中的 WeakHashM...

  • JAVA引用(WeakHashMap、Cleaner)

    Reference 引用类 强引用、软引用、弱引用、虚引用 软引用、弱引用、虚引用,可以配合ReferenceQu...

  • WeakHashMap

    WeakHashMap 总体介绍 在Java集合框架系列文章的最后,笔者打算介绍一个特殊的成员:WeakHashM...

网友评论

      本文标题:聊聊Java WeakHashMap背后的事情

      本文链接:https://www.haomeiwen.com/subject/qtrafktx.html