目录
WeakHashMap,从名字可以看出它是某种 Map。它的特殊之处在于 WeakHashMap 里的entry可能会被GC自动删除,即使程序员没有调用remove()或者clear()方法。
更直观的说,当使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情况:
1. 调用两次size()方法返回不同的值;
2. 两次调用isEmpty()方法,第一次返回false,第二次返回true;
3. 两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key;
4. 两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象。
遇到这么奇葩的现象,你是不是觉得使用者一定会疯掉?其实不然,WeakHashMap 的这个特点特别适用于需要缓存的场景。在缓存场景下,由于内存是有限的,不能缓存所有对象;对象缓存命中可以提高系统效率,但缓存MISS也不会造成错误,因为可以通过计算重新得到。
要明白 WeakHashMap 的工作原理,还需要引入一个概念:弱引用(WeakReference)。我们都知道Java中的内存是通过GC自动管理的,GC会在程序运行过程中自动判断哪些对象是可以被回收的,并在合适的时机进行内存释放。GC判断某个对象是否可被回收的依据是,是否有有效的引用指向该对象。如果没有有效引用指向该对象(基本意味着不存在访问该对象的方式),那么该对象就是可回收的。这里的有效引用并不包括弱引用。也就是说,虽然弱引用可以用来访问对象,但进行垃圾回收时弱引用并不会被考虑在内,仅有弱引用指向的对象仍然会被GC回收。
WeakHashMap 内部是通过弱引用来管理entry的,弱引用的特性对应到 WeakHashMap 上意味着什么呢?将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收,除非在 WeakHashMap 之外还有对该key的强引用。
关于强引用,弱引用等概念请参见《Java 四种引用类型》。
如果你看过前几篇关于 Map 和 Set 的讲解,一定会问:既然有 WeakHashMap,是否有 WeekHashSet 呢?答案是没有。不过Java Collections工具类给出了解决方案,Collections.newSetFromMap(Map
- // 将WeakHashMap包装成一个Set
- Set
- new WeakHashMap
-
- // Collections.newSetFromMap()用于将任何Map包装成一个Set
- public static
Set newSetFromMap(Map map) { - return new SetFromMap<>(map);
- }
-
- private static class SetFromMap
extends AbstractSet - implements Set
, Serializable - {
- private final Map
m; // The backing map - private transient Set
s; // Its keySet - SetFromMap(Map
map) { - if (!map.isEmpty())
- throw new IllegalArgumentException("Map is non-empty");
- m = map;
- s = map.keySet();
- }
- public void clear() { m.clear(); }
- public int size() { return m.size(); }
- public boolean isEmpty() { return m.isEmpty(); }
- public boolean contains(Object o) { return m.containsKey(o); }
- public boolean remove(Object o) { return m.remove(o) != null; }
- public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; }
- public Iterator
iterator() { return s.iterator(); } - public Object[] toArray() { return s.toArray(); }
- public
T[] toArray(T[] a) { return s.toArray(a); } - public String toString() { return s.toString(); }
- public int hashCode() { return s.hashCode(); }
- public boolean equals(Object o) { return o == this || s.equals(o); }
- public boolean containsAll(Collection> c) {return s.containsAll(c);}
- public boolean removeAll(Collection> c) {return s.removeAll(c);}
- public boolean retainAll(Collection> c) {return s.retainAll(c);}
- // addAll is the only inherited implementation
-
- // Override default methods in Collection
- @Override
- public void forEach(Consumer super E> action) {
- s.forEach(action);
- }
- @Override
- public boolean removeIf(Predicate super E> filter) {
- return s.removeIf(filter);
- }
-
- @Override
- public Spliterator
spliterator() {return s.spliterator();} - @Override
- public Stream
stream() {return s.stream();} - @Override
- public Stream
parallelStream() {return s.parallelStream();} -
- private static final long serialVersionUID = 2454657854757543876L;
-
- private void readObject(java.io.ObjectInputStream stream)
- throws IOException, ClassNotFoundException
- {
- stream.defaultReadObject();
- s = m.keySet();
- }
- }

从图中可以看出:WeakHashMap继承于AbstractMap,并且实现了Map接口。
- // 默认的初始容量是16,必须是2的幂。
- private static final int DEFAULT_INITIAL_CAPACITY = 16;
-
- // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
- private static final int MAXIMUM_CAPACITY = 1 << 30;
-
- // 默认加载因子
- private static final float DEFAULT_LOAD_FACTOR = 0.75f;
-
- // 存储数据的Entry数组,长度是2的幂。
- // WeakHashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
- private Entry[] table;
-
- // WeakHashMap的大小,它是WeakHashMap保存的键值对的数量
- private int size;
-
- // WeakHashMap的阈值,用于判断是否需要调整WeakHashMap的容量(threshold = 容量*加载因子)
- private int threshold;
-
- // 加载因子实际大小
- private final float loadFactor;
-
- // queue保存的是“已被GC清除”的“弱引用的键”。
- // 弱引用和ReferenceQueue 是联合使用的:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中
- private final ReferenceQueue
-
- // WeakHashMap被改变的次数
- int modCount;
- // 指定“容量大小”和“加载因子”的构造函数
- public WeakHashMap(int initialCapacity, float loadFactor) {
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal Initial Capacity: "+
- initialCapacity);
- // WeakHashMap的最大容量只能是MAXIMUM_CAPACITY
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
-
- if (loadFactor <= 0 || Float.isNaN(loadFactor))
- throw new IllegalArgumentException("Illegal Load factor: "+
- loadFactor);
- // 找出“大于initialCapacity”的最小的2的幂
- int capacity = 1;
- while (capacity < initialCapacity)
- capacity <<= 1;
- // 创建Entry数组,用来保存数据
- table = newTable(capacity);
- // 设置“加载因子”
- this.loadFactor = loadFactor;
- // 设置“WeakHashMap阈值”,当WeakHashMap中存储数据的数量达到threshold时,就需要将WeakHashMap的容量加倍。
- threshold = (int)(capacity * loadFactor);
- }
-
- // 指定“容量大小”的构造函数
- public WeakHashMap(int initialCapacity) {
- this(initialCapacity, DEFAULT_LOAD_FACTOR);
- }
-
- // 默认构造函数。
- public WeakHashMap() {
- this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
- }
-
- // 包含“子Map”的构造函数
- public WeakHashMap(Map extends K, ? extends V> m) {
- this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
- DEFAULT_INITIAL_CAPACITY),
- DEFAULT_LOAD_FACTOR);
- // 将m中的全部元素逐个添加到WeakHashMap中
- putAll(m);
- }
-
- private Entry
[] newTable(int n) { - return (Entry
[]) new Entry,?>[n]; - }
- private static class Entry
extends WeakReference - V value;
- final int hash;
- Entry
next; -
- /**
- * Creates new entry.
- */
- Entry(Object key, V value,
- ReferenceQueue
- int hash, Entry
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();
- }
- }
可以看到Entry是继承WeakReference的,我们结合WeakReference再看一下:
- public class WeakReference
extends Reference { - /**
- * Creates a new weak reference that refers to the given object. The new
- * reference is not registered with any queue.
- * 创建一个新的弱应用给传入的对象,这个新的引用不注册任何队列
- *
- * @param referent object the new weak reference will refer to
- */
- public WeakReference(T referent) {
- super(referent);
- }
-
- /**
- * Creates a new weak reference that refers to the given object and is
- * registered with the given queue.
- * 创建一个新的弱应用给传入的对象,这个新的引用注册给一个给定的队列
- *
- * @param referent object the new weak reference will refer to
- * @param q the queue with which the reference is to be registered,
- * or null if registration is not required
- */
- public WeakReference(T referent, ReferenceQueue super T> q) {
- super(referent, q);
- }
- }
我们发现在WeakHashMap中把key注册给了WeakReference,也就是说在WeakHashMap中key是一个弱引用。
- public V put(K key, V value) {
- // 如果key是null则给定一个空的对象进行修饰
- Object k = maskNull(key);
- // 计算key的hash
- int h = hash(k);
- // 获取table
- Entry
[] tab = getTable(); - // 根据hash找到数组下标
- int i = indexFor(h, tab.length);
- // 找到链表中元素位置
- for (Entry
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
e = tab[i]; - tab[i] = new Entry<>(k, value, queue, h, e);
- if (++size >= threshold) // 是否达到阀值达到阀值就扩容
- resize(tab.length * 2);
- return null;
- }
-
- private Entry
[] getTable() { - expungeStaleEntries();
- return table;
- }
-
- private void expungeStaleEntries() {
- // 从 ReferenceQueue中拉取元素
- for (Object x; (x = queue.poll()) != null; ) {
- synchronized (queue) {
- @SuppressWarnings("unchecked")
- Entry
e = (Entry) x; - int i = indexFor(e.hash, table.length);
-
- Entry
prev = table[i]; - Entry
p = prev; - while (p != null) {
- Entry
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
- // 拿到entry的值赋值为null帮助GC
- e.value = null; // Help GC
- size--;
- break;
- }
- prev = p;
- p = next;
- }
- }
- }
- }
expungeStaleEntries 就是WeakHashMap的核心了,它承担着Map中死对象的清理工作。原理就是依赖WeakReference和ReferenceQueue的特性。在每个WeakHashMap都有个ReferenceQueue queue,在Entry初始化的时候也会将queue传给WeakReference,这样当某个key可以失去所有强引用之后,其key对应的WeakReference对象会被放到queue里,有了queue就知道需要清理哪些Entry了。
这里也是整个WeakHashMap里唯一加了同步的地方。除了上文说的到resize中调用了expungeStaleEntries(),size()中也调用了这个清理方法。另外 getTable()也调了,这就意味着几乎所有其他方法都间接调用了清理。
- // 将"m"的全部元素都添加到WeakHashMap中
- public void putAll(Map extends K, ? extends V> m) {
- int numKeysToBeAdded = m.size();
- if (numKeysToBeAdded == 0)
- return;
-
- // 计算容量是否足够,
- // 若“当前实际容量 < 需要的容量”,则将容量x2。
- if (numKeysToBeAdded > threshold) {
- int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
- if (targetCapacity > MAXIMUM_CAPACITY)
- targetCapacity = MAXIMUM_CAPACITY;
- int newCapacity = table.length;
- while (newCapacity < targetCapacity)
- newCapacity <<= 1;
- if (newCapacity > table.length)
- resize(newCapacity);
- }
-
- // 将“m”中的元素逐个添加到WeakHashMap中。
- for (Map.Entry extends K, ? extends V> e : m.entrySet())
- put(e.getKey(), e.getValue());
- }
- // 重新调整WeakHashMap的大小,newCapacity是调整后的单位
- void resize(int newCapacity) {
- Entry[K, V] oldTable = getTable();
- int oldCapacity = oldTable.length;
- if (oldCapacity == MAXIMUM_CAPACITY) {
- threshold = Integer.MAX_VALUE;
- return;
- }
-
- // 新建一个newTable,将“旧的table”的全部元素添加到“新的newTable”中,
- // 然后,将“新的newTable”赋值给“旧的table”。
- Entry[K, V] newTable = new Table(newCapacity);
- transfer(oldTable, newTable);
- table = newTable;
-
- if (size >= threshold / 2) {
- threshold = (int)(newCapacity * loadFactor);
- } else {
- // 删除table中“已被GC回收的key对应的键值对”
- expungeStaleEntries();
- transfer(newTable, oldTable);
- table = oldTable;
- }
- }
-
- // 将WeakHashMap中的全部元素都添加到newTable中
- private void transfer(Entry[] src, Entry[] dest) {
- for (int j = 0; j < src.length; ++j) {
- Entry
e = src[j]; - src[j] = null;
- while (e != null) {
- Entry
next = e.next; - Object key = e.get();
- if (key == null) {
- e.next = null; // Help GC
- e.value = null; // " "
- size--;
- } else {
- int i = indexFor(e.hash, dest.length);
- e.next = dest[i];
- dest[i] = e;
- }
- e = next;
- }
- }
- }
- // 删除“键为key”元素
- public V remove(Object key) {
- Object k = maskNull(key);
- // 获取哈希值。
- int h = hash(k);
- Entry
[] tab = getTable(); - int i = indexFor(h, tab.length);
- Entry
prev = tab[i]; - Entry
e = prev; -
- // 删除链表中“键为key”的元素
- // 本质是“删除单向链表中的节点”
- while (e != null) {
- Entry
next = e.next; - if (h == e.hash && eq(k, e.get())) {
- modCount++;
- size--;
- if (prev == e)
- tab[i] = next;
- else
- prev.next = next;
- return e.value;
- }
- prev = e;
- e = next;
- }
-
- return null;
- }
-
- // 删除“键值对”
- boolean removeMapping(Object o) {
- if (!(o instanceof Map.Entry))
- return false;
- Entry
[] tab = getTable(); - Map.Entry,?> entry = (Map.Entry,?>)o;
- Object k = maskNull(entry.getKey());
- int h = hash(k);
- int i = indexFor(h, tab.length);
- Entry
prev = tab[i]; - Entry
e = prev; -
- // 删除链表中的“键值对e”
- // 本质是“删除单向链表中的节点”
- while (e != null) {
- Entry
next = e.next; - if (h == e.hash && e.equals(entry)) {
- modCount++;
- size--;
- if (prev == e)
- tab[i] = next;
- else
- prev.next = next;
- return e;
- }
- prev = e;
- e = next;
- }
-
- return false;
- }
-
- // 清空WeakHashMap,将所有的元素设为null
- public void clear() {
- while (queue.poll() != null)
- ;
-
- modCount++;
- Arrays.fill(table, null);
- size = 0;
-
- while (queue.poll() != null)
- ;
- }
- // 获取key对应的value
- public V get(Object key) {
- Object k = maskNull(key);
- // 获取key的hash值。
- int h = hash(k);
- Entry
[] tab = getTable(); - int index = indexFor(h, tab.length);
- Entry
e = tab[index]; - // 在“该hash值对应的链表”上查找“键值等于key”的元素
- while (e != null) {
- if (e.hash == h && eq(k, e.get()))
- return e.value;
- e = e.next;
- }
- return null;
- }
-
- // WeakHashMap是否包含key
- public boolean containsKey(Object key) {
- return getEntry(key) != null;
- }
-
- // 返回“键为key”的键值对
- Entry
getEntry(Object key) { - Object k = maskNull(key);
- int h = hash(k);
- Entry
[] tab = getTable(); - int index = indexFor(h, tab.length);
- Entry
e = tab[index]; - while (e != null && !(e.hash == h && eq(k, e.get())))
- e = e.next;
- return e;
- }