本文先介绍WeakHashMap的四种引用,然后再从源码角度分析WeakHashMap的继承结构,基本属性等内容。
WeakHashMap并没有什么特殊的数据结构,存在本身主要是为了优化JVM,使得JVM的垃圾回收器(GC)能够更“智能”地回收“无用”的对象。WeakHashMap涉及到Java中的引用类型问题,下面具体介绍Java的四种引用类型。
1、强引用(StrongReference)
功能:一个对象如果只有强引用,那么垃圾回收器绝不会回收它,即使当内存不足时,JVM宁愿抛出内存不足的异常,也不会去回收这些对象。
使用场景:平常大部分都会使用强引用,除非显性释放对象(置为null),否则GC不会回收该对象。
由于强引用的一些特性,Java还提供了另外三种引用:软引用,弱引用,虚引用;这三种引用都位于java.lang.ref包中,继承于Reference类,以下将对Reference的源码进行简要分析,再介绍这三种引用。
Reference源码分析:
基本属性
private T referent; //引用指向的对象 /* Treated specially by GC */
ReferenceQueue super T> queue; //ReferenceQueue类,适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中。
Reference next;
transient private Reference discovered; /* used by VM */
private static Reference pending = null;
构造方法
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
}
在源码中,我们也可以看到Reference有4种状态: Active,Pending,Enqueued,Inactive;
关于Active:基本新创建的对象都是这个状态,在 GC 检测到引用对象已经到达合适的可达性状态时,GC 会根据引用对象是否在创建时制定ReferenceQueue
参数进行状态转移,如果指定了,那么转移到Pending
,如果没指定,转移到Inactive;在该状态中:
//若构造参数中没有指定queue,则queue为ReferenceQueue.NULL,否则为构造参数传递的queue
Active: queue = ReferenceQueue || queue =ReferenceQueue.NULL
next = null.
关于 Pending:pending-Reference列表中的引用都是这个状态,它们等着被内部线程ReferenceHandler
处理(会调用ReferenceQueue.enqueue
方法)。没有注册的实例不会进入这个状态。在这个状态中:
//构造参数传递的queue
Pending: queue = ReferenceQueue
next = 该queue的下一个引用,若为该队列的最后一个,则为this;
关于Enqueued:调用ReferenceQueue.enqueued
方法后的引用处于这个状态中。没有注册的实例不会进入这个状态。在这个状态中
enqueued: queue = ReferenceQueue.ENQUEUED
next = 该queue的下一个引用,若为该队列的最后一个,则为this;
关于Inactive:最终状态,处于这个状态的引用对象,状态不会在改变。在这个状态中
Inactive: queue = ReferenceQueue.NULL
next = this;
2、软引用(SoftReference)
功能:如果一个对象只有软引用,则在内存足够的情况下,不会进行回收,当内存不足时,GC会进行回收操作;
应用场景:可用于有可能会在创建后使用的对象,为了内存消耗会使用软引用;
3、弱引用(WeakReference)
功能:弱引用的生命周期比软引用更短,GC在扫描时发现对象只有弱引用,不论内存是否足够,都会进行回收;
使用场景:适用于生命周期更短,对内存更加敏感的场景中,比如占用内存很大的Map,java API中就提供了WeakHashMap使用,就会使得大Map被及时清理掉。
4、虚引用 (PhantomReference)
功能:形同虚设,保存对象的能力最弱,不会影响对象的生命周期,作用是跟踪GC收集对象的活动情况;
使用场景:判断对象是否被回收了
PhantomReference必须要和ReferenceQueue联合使用,SoftReference和WeakReference可以选择和ReferenceQueue联合使用也可以不选择,这使他们的区别之一。
public class WeakHashMap
extends AbstractMap
implements Map {
继承于AbstractMap类,实现了Map接口;区别于HashMap,没有实现Cloneable和Serializable接口,不能实现拷贝和序列化;
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
//1 指定初始容量和加载因子
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);
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
//2 指定初始容量,加载因子默认
public WeakHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//3 默认构造方法
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//4 子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);
putAll(m);
}
put()
public V put(K key, V value) {
//对key是否为空进行检查,如果为null,用new的Object进行替换
Object k = maskNull(key);
int h = hash(k);
Entry[] tab = getTable();
int i = indexFor(h, tab.length);
//检查此key是否存在,存在则更新value,不存在则将该节点头插到该位置
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;
}
//检查key是否为null,为空用new的Object进行替换
private static Object maskNull(Object key) {
return (key == null) ? NULL_KEY : key;
// private static final Object NULL_KEY = new Object();
}
其他方法与HashMap基本一致,主要差别在于,WeakHashMap对于key是否为null进行了检查,为空的话,将其替换为new的Object常量;put和get方法中的getTable都会调用expungeStaleEntries()
private Entry[] getTable() {
expungeStaleEntries();
return table;
}
还有一个size()方法,在返回节点个数时,还有一步操作:expungeStaleEntries() ,删除过时节点,然后再返回个数;
public int size() {
if (size == 0)
return 0;
expungeStaleEntries();
return size;
}
//该方法在增删改查方法中基本上都调用了
private void expungeStaleEntries() {
//所有的Entry在创建时,都先传入该queue
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry e = (Entry) x;//e为要清理的对象
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
//将e的value赋为null,帮助GC回收强引用的value
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
随时间推移,键值对会减少 -> GC垃圾回收
WeakHashMap与ReferenceQueue结合使用,使用步骤:
1、创建WeakHashMap将键值对存放在数组链表上;
2、当发生GC时会判断key是否有引用,若没有引用,就会回收key,意味着从集合中回收,回收同时,将该对象加入ReferenceQueue;
3、在操作WeakHashMap时,会首先同步table和Queue,table存放的是所有键值对, Queue存放的是被回收的键值对,在table中删除Queue中有的键值对;
WeakHashMap和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以为null。不过WeakHashMap的键时“弱键”,当WeakHashMap某个键不再正常使用时,会被从WeakHashMap自动删除。更精确的说,对于一个给定的键,其映射的存在并不能阻止垃圾回收器对该键的丢弃,这就使该键称为被终止的,被终止,然后被回收,这样,这就可以认为该键值对应该被WeakHashMap删除。因此,WeakHashMap使用了弱引用作为内部数据的存储方案,,WeakHashMap可以作为简单缓存表的解决方案,当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对。如果需要用一张很大的Map作为缓存表时,那么可以考虑使用WeakHashMap。
总结参考自博文:https://blog.csdn.net/u010412719/article/details/52034079