HashMap中如果一个键指向的对象没有其他任何引用指向它,也不会被GC回收,被map的桶接管了,存在强引用,只能手动remove。如果想要这种键值对被自动回收怎么办?带着问题看一下WeakHashMap的源码
WeakHashMap的属性和HashMap中基本一样,不过WeakHashMap没有红黑树相关 *** 作,还多了个ReferenceQueue。
ReferenceQueue存的是什么呢?这里可以推出是那些指向被回收的对象的弱引用。
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; Entry[] table; private int size; private int threshold; private final float loadFactor; private final ReferenceQueue
这里主要要看一下Entry
看一下Entry
private static class Entryextends WeakReference
分析出几点:
- Entry
extends WeakReference说明Entry 本身是一个弱引用,那它的构造函数一定能返回一个弱引用,指向的是传给父类构造函数的第一个参数。同时绑定了一个引用队列。当指向的对象被回收后,弱引用就入队。 - Entry
中包含一个属性Entry next,next指向的是下一个Entry 对象,符合链表的定义。 - 第一次见到引用自身还能携带属性的,其中一个属性next又指向了同类的另一个引用,感觉有点绕。
偷来别人的图看一下:
WeakHashMap的构造函数有两个参数,table的初始容量,加载因子,生成了table数组,元素类型是Entry
一般情况下用无参构造就行了。
public WeakHashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Initial Capacity: "+ initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor); int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; table = newTable(capacity); this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); } public WeakHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public WeakHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } private EntryWeakHashMap的一些工具方法[] newTable(int n) { return (Entry []) new Entry,?>[n]; }
private static final Object NULL_KEY = new Object(); private static Object maskNull(Object key) { return (key == null) ? NULL_KEY : key; } static Object unmaskNull(Object key) { return (key == NULL_KEY) ? null : key; } private static boolean eq(Object x, Object y) { return x == y || x.equals(y); } final int hash(Object k) { int h = k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } private static int indexFor(int h, int length) { return h & (length-1); }
关键方法expungeStaleEntries,作用是移除Entry
这些Entry
private void expungeStaleEntries() { //取出已经入队的弱引用x (Entryx) for (Object x; (x = queue.poll()) != null; ) { //一整个是一个同步块 synchronized (queue) { @SuppressWarnings("unchecked") //现在e是出队的那个弱引用,指向的是一个原本有强引用key指向的对象 Entry e = (Entry ) x; //e里面的属性hash配合table的长度来确定e当前在哪个桶 int i = indexFor(e.hash, table.length); //prev是桶的第一个元素 Entry prev = table[i]; //经典的链表双指针遍历法 Entry p = prev; //p往前走,prev跟在后面,直到p==e为止 while (p != null) { Entry next = p.next; //找到了等于e的那个节点p if (p == e) { //这里说明e就是桶里的第一个元素 if (prev == e) //直接置为next,e就从map中移除了 table[i] = next; else //不是第一个元素,跳过e prev.next = next; // Must not null out e.next; // stale entries may be in use by a HashIterator //到上面为止就已经完成了移除e e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
e这个东西是从引用队列里拿出来的,是一个弱引用,是对原本key指向的实际对象的一个弱引用。正是由于实际对象只剩这一个弱引用才会被GC回收。
然后e又是map中的一个元素,它本身是被强引用的,所以才有expungeStaleEntries()方法来从map中删除它的一个过程。
这里留了两个问题想不清楚:
- Must not null out e.next 是什么意思
- e.value = null; // Help GC 为什么要这样写
public V put(K key, V value) { //null也可以存进来 Object k = maskNull(key); //计算key的hash int h = hash(k); //getTable()返回table,返回之前要expungeStaleEntries EntryRESIZE[] tab = getTable(); //计算出table中的下标 int i = indexFor(h, tab.length); //遍历链表,如果找到key一样的,并且值不相等的,给新值 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]; //插在了链表头,next指向原来的第一个元素 tab[i] = new Entry<>(k, value, queue, h, e); //判断size是否达到扩容条件 if (++size >= threshold) resize(tab.length * 2); return null; } private Entry [] getTable() { expungeStaleEntries(); return table; }
void resize(int newCapacity) { //拿到老表 EntryTRANSFER[] oldTable = getTable(); //老表长度看一下 int oldCapacity = oldTable.length; //如果老表长度等于最大容量 if (oldCapacity == MAXIMUM_CAPACITY) { //门槛设置为最大,这样put *** 作就不会总是进resize方法了 threshold = Integer.MAX_VALUE; //扩不了了,结束 return; } //来张新表 Entry [] newTable = newTable(newCapacity); //元素迁移 transfer(oldTable, newTable); table = newTable; //完成扩容之后,如果size大于门槛的1/2,门槛就按正常的设置为新的门槛 //如果size不大于门槛的1/2,(size明显缩水了,可能是其他 *** 作中的 //expungeStaleEntries导致的,transfer里面也可能导致size缩水 if (size >= threshold / 2) { threshold = (int)(newCapacity * loadFactor); } else { expungeStaleEntries(); transfer(newTable, oldTable); table = oldTable; } }
private void transfer(Entry[] src, Entry [] dest) { //老表中的所有元素搬到新表 for (int j = 0; j < src.length; ++j) { //j号桶的第一个元素 Entry e = src[j]; //老表的j号桶直接清空 src[j] = null; while (e != null) { //拿到e的下一个元素 Entry next = e.next; //对e做一些处理,e.get返回弱引用指向的实际对象,也就是key Object key = e.get(); //如果key什么都没拿到 if (key == null) { //e不要了,把它的next和value都给null size-- 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; } } }
这里又出现了Help GC的问题,不是很懂。
为什么这里可以写e.next = null了?而expungeStaleEntries方法里不写呢(Must not null out e.next说的是不是e.next不可以为null?)
containsValuepublic boolean containsValue(Object value) { if (value==null) return containsNullValue(); //主要看一下遍历方式:先数组,再链表 Entryget[] tab = getTable(); for (int i = tab.length; i-- > 0;) for (Entry e = tab[i]; e != null; e = e.next) if (value.equals(e.value)) return true; return false; }
public V get(Object key) { //key为null也能查出来,NULL_KEY是一个 static final,计算出的hash总是一样 Object k = maskNull(key); int h = hash(k); Entry总结[] tab = getTable(); int index = indexFor(h, tab.length); Entry e = tab[index]; //找到对应的桶,走一下链表 while (e != null) { //看一下这里是怎么拿到key的,key不是e的属性,而是e弱引用的对象,直接get if (e.hash == h && eq(k, e.get())) return e.value; e = e.next; } return null; }
- 关键是理解Entry
的结构,WeakHashMap的用法。 - 理清引用关系很重要。
- 留下了一个问题:expungeStaleEntries()里面e.value = null; // Help GC是如何help GC的?为什么不能e.next=null?(猜测可能和stale entries may be in use by a HashIterator这一句有关)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)