涉及问题:
- WeakHashMap和ThreadLocal的异同,两者分别怎样清理失效的Entry?
- 为什么ThreadLocal会内存泄漏?原理类似的WeakHashMap会内存泄漏吗?
在网上查没看懂,以下是自己看源码后的一些思考,很口语化且不保证一定正确,欢迎纠错
WeakHashMap源码
WeakHashMap.Entry直接继承自WeakReference,是弱引用本身,它持有的referent指向对象A(key)。另外,WeakHashMap.Entry中持有强引用value
对象A不再被外部引用后会被回收,回收时WeakHashMap.Entry对象作为弱引用加入引用队列queue中:
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
/**
* 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;
}
// ...
}
// ...
}
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
// ...
}
public abstract class Reference<T> {
private T referent; /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
// ...
}
expunge:清除
Stale:不新鲜的
Entries:条目(键值对)
失效条目指这样的条目:Entry(key=null, value=A)
get()、put()、size()等方法都会调用expungeStaleEntries()。expungeStaleEntries()会把队列中的所有引用取出。取出的Object x的实际类型是WeakHashMap.Entry,转换为Entry<K,V>类型后,可以从中取出强引用value,将value置null,并从Map中把该Entry去除(此处即使不让value=null,value指向的值对象应该也能被回收,因为Map不再持有该Entry -> Entry被回收 -> Entry中value曾指向的值对象被回收,只是得经过多次GC才行。所以手动value=null应该是为了使值对象更快回收?):
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
// 清除所有失效的条目
private void expungeStaleEntries() {
// 轮询引用队列,移除所有条目
for (Object x; (x = queue.poll()) != null; ) {
// 同步队列以保证线程安全
synchronized (queue) {
@SuppressWarnings("unchecked") // 抑制类型转换警告
Entry<K,V> e = (Entry<K,V>) x; // 将Object转换为Entry类型
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;
}
// 此处不能将e.next置为null,因为可能被迭代器使用
e.value = null; // 清除值引用,帮助垃圾回收
size--; // 减少有效条目计数
break;
}
// 移动到下一个节点继续查找
prev = p;
p