Java 弱引用 - WeakHashMap
Java 弱引用
Java中有如下四种类型的引用:
- 强引用(Strong Reference)
- 弱引用(WeakReference)
- 软引用(SoftReference)
- 虚引用(PhantomReference)
在Java里, 当一个对象o被创建时, 它被放在Heap里. 当GC运行的时候, 如果发现没有任何引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1)没有任何引用指向它 2)GC被运行。
在现实情况写代码的时候, 我们往往通过把所有指向某个对象的referece置空来保证这个对象在下次GC运行的时候被回收 (可以用java -verbose:gc来观察gc的行为)
Object c = new Car();
c=null;
但是, 手动置空对象对于程序员来说, 是一件繁琐且违背自动回收的理念的. 对于简单的情况, 手动置空是不需要程序员来做的, 因为在java中, 对于简单对象, 当调用它的方法执行完毕后, 指向它的引用会被从stack中popup, 所以他就能在下一次GC执行时被回收了.
但是, 也有特殊例外. 当使用cache的时候, 由于cache的对象正是程序运行需要的, 那么只要程序正在运行, cache中的引用就不会被GC(或者说, cache中的reference拥有了和主程序一样的life cycle)。 那么随着cache中的reference越来越多, GC无法回收的object也越来越多, 无法被自动回收。当这些object需要被回收时, 回收这些object的任务只有交给程序编写者了。然而这却违背了GC的本质(自动回收可以回收的objects).
所以, java中引入了weak reference. 相对于前面举例中的strong reference:
Object c = new Car(); //只要c还指向car object, car object就不会被回收
当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收。weak reference的语法是:
WeakReference<Car> weakCar = new WeakReference(Car)(car);
当要获得weak reference引用的object时, 首先需要判断它是否已经被回收:
weakCar.get();
如果此方法为空, 那么说明weakCar指向的对象已经被回收了.
java.lang.ref.Reference
java.lang.ref.Reference 为 软(soft)引用、弱(weak)引用、虚(phantom)引用的父类。
下面简单的分析下 Reference 的源码。其构造函数为,
//referent 为引用指向的对象
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
第二个构造函数需要提供一个 ReferenceQueue 对象,也就是引用队列。在weak reference指向的对象被回收后, weak reference本身其实也就没有用了。java提供了一个ReferenceQueue来保存这些所指向的对象已经被回收的reference 。
ReferenceHandler 线程
// eg1
public static void test() throws Exception{
Object o = new Object();
// 默认的构造函数,会使用ReferenceQueue.NULL 作为queue
WeakReference<Object> wr = new WeakReference<Object>(o);
System.out.println(wr.get() == null);
o = null;
System.gc();
System.out.println(wr.get() == null);
}
这个线程在Reference类的static构造块中启动,并且被设置为高优先级和daemon状态。此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
通过这2点,我们来看整个过程:
pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。
结合代码eg1中的 o = null;
这一句,它使得o对象满足垃圾回收的条件,并且在后边显式的调用了 System.gc()
,垃圾收集进行的时候会标记WeakReference
所referent的对象o为不可达(使得wr.get()==null
),并且通过 赋值给pending
,触发ReferenceHandler
线程处理pending
。
ReferenceHandler
线程要做的是将pending
对象enqueue
,但默认我们所提供的queue,也就是从构造函数传入的是null,实际是使用了ReferenceQueue.NULL
,Handler
线程判断queue为ReferenceQueue.NULL
则不进行操作,只有非ReferenceQueue.NULL
的queue才会将Reference进行enqueue。
ReferenceQueue.NULL
相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL
的queue。
WeakHashMap.Entry
WeakHashMap与HashMap的签名与构造函数一样,这里就不介绍了,这里重点介绍下Entry这个内部对象,因为其保存具体key-value对,把它弄清楚了,其他的就问题不大了。
/**
* 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;
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;
}
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 ((k==null ? 0 : k.hashCode()) ^
(v==null ? 0 : v.hashCode()));
}
public String toString() {
return getKey() + "=" + getValue();
}
}
WeakHashMap.expungeStaleEntries
WeakHashMap的 put, size, clear
都会间接或直接的调用到 expungeStaleEntries()
方法。
expungeStaleEntries
,此方法的作用就是将 queue中陈旧的Reference进行删除,因为其内部的referent都已经不可达了。所以也将这个WeakReference包装的key从map中删除。
/**
* 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; // e entry 实体,虚引用实例
int i = indexFor(e.hash, table.length);
Entry<K, V> prev = table[i];
Entry<K, V> p = prev;
while (p != null) { // while 循环遍历冲突链
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;
}
}
}
}
参考:http://hongjiang.info/java-referencequeue/
http://puretech.iteye.com/blog/2008663
=============END=============