Java的WeakReference与WeakHashMap

首先看看 WeakReference

wiki 上 Weak reference 的一个例子:

	public class ReferenceTest {
	public static void main(String[] args) throws InterruptedException {
 
            WeakReference r = new WeakReference(new String("I'm here"));
            WeakReference sr = new WeakReference("I'm here");
            System.out.println("before gc: r=" + r.get() + ", static=" + sr.get());
            System.gc();
            Thread.sleep(100);
 
            // only r.get() becomes null
            System.out.println("after gc: r=" + r.get() + ", static=" + sr.get());
 
	}
}


输出:
before gc: r=I'm here, static=I'm here
after gc: r=null, static=I'm here

为什么“only r.get() becomes null”?

最容易想到的原因是 string-pool
1.new String("I'm here") 是在堆上创建了一个 String 对象,由于程序中没有对这个对象的强引用,因此会被回收掉
如果是下面这样则不会回收:
String str = new String("I'm here");
WeakReference r = new WeakReference(str);
2.而 直接量"I'm here" 是从 string-pool 中取得的,它被 string-pool 强引用了,因此不会被回收

但 stackoverflow 上有人说本质上不是这个原因(链接:http://stackoverflow.com/questions/14494875/weakreference-string-didnt-garbage-collected-how):

It is NOT because of string pooling per se.

The real reason is that the ReferenceTest class has an implicit hard reference to the String object that represents the "I'm here"literal. That hard reference means that the weak reference in sr won't be broken by the garbage collection.


是因为 ReferenceTest 对 直接量 "I'm here" 有强引用?没看懂。。

看完了弱引用,再看看 Java 中其他的几种引用类型

1.强引用
这个没啥好说,通常情况下的引用是强引用

2.软引用
与弱引用很相似,但生命周期比弱引用要长。在内存不是那么紧张时,不会被回收

3.弱引用
主要是方便和加快垃圾回收,避免对象长驻内存
如果一个对象的所有引用都是弱引用(称为 weakly reachable)的话,那一旦被垃圾回收器注意到,就会被回收
wiki 上指出弱引用的应用场合:
a.在“引用计数法”的垃圾回收机制中,能避免“循环引用”,因为 Weak references are not counted in reference counting
b."key-value"形式的数据结构中,key 可以是弱引用。例如 Map
c.观察者模式(特别是事件处理)中,观察者或者 listener 如果采用弱引用,则不需要显式的移除
d.缓存

4.虚引用
虚引用对对象的引用作用很弱,你无法通过虚引用来访问一个对象:虚引用的 get 方法总是返回 null
虚引用主要用来跟踪对象被垃圾回收的活动
它跟弱引用的区别是,一旦对象是  weakly reachable,弱引用就会被加入到 Reference queues 当中,这发生在 finalize 和 GC 之前;
而虚引用,只有在对象真正被清除出内存时,才会被加入
它有两大作用:
a.通过虚引用可以知道一个对象何时被清除出内存。事实上,这也是唯一的途径
b.防止对象在 finalize 方法中被重新“救活”(可参考《深入理解 Java 虚拟机》一书)

接下来我们看看 Java 的 WeakHashMap,它的 key 是弱引用,在 key 不再需要的时候,就可以把 key-value(下面统称为entry) 从 WeakHashMap 当中移除,并最终被回收

什么情况下需要用到  WeakHashMap?

例如:

public class SocketManager {
    private Map<Socket,User> m = new HashMap<Socket,User>();
 
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
    public void removeUser(Socket s) {
        m.remove(s);
    }
}
 
SocketManager socketManager;
...
socketManager.setUser(socket, user);


上述程序中, 我们把 socket 称为 transient objects,而 User 称为 metadata
问题是:
当 socket 不再需要(例如关闭了)时,它和它关联的 User 仍在 Map 中,这就造成了内在泄漏(the lifetime of the Socket-User mapping should be matched to the lifetime of the Socket, but the language does not provide any easy means to enforce this rule)

解决办法就是使用 WeakHashMap:

public class SocketManager {
    private Map<Socket,User> m = new WeakHashMap<Socket,User>();
 
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
}


WeakHashMap 中, value 是强引用。这会带来一个问题,当 key 被回收时,value 仍存活
因此,要加上一个类似 listener 的功能,当 key 回收时,把 WeakReference 放入  Reference queues,执行相关的清理操作
参见 WeakReference 的构造函数:WeakReference(T referent, ReferenceQueue<? super T> q)

那 entry 的删除是什么时候执行?
看看 WeakHashMap 的源码:
private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {
        private V value;
        private final int hash;
        private Entry<K,V> next;

		
        Entry(K key, V value,
	      ReferenceQueue<K> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);		//可以看到,entry 的 key 是被 WeakReference 封装起来的
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
		
		//...
}


public int size() {
	if (size == 0)
		return 0;
	expungeStaleEntries();
	return size;
}

public V put(K key, V value) {
	K k = (K) maskNull(key);
	int h = HashMap.hash(k.hashCode());
	Entry[] tab = getTable();
	//...
}

 private Entry[] getTable() {
	expungeStaleEntries();
	return table;
}

/*
这个方法就是把处于  Reference queues 中的 entry 从 WeakHashMap 中移除
其实是很简单的链表操作,例如
a -> b -> c
删除 b ,就让 a 直接指向 c:a -> c
注意到这只是把 b 从链表中删除并且使 b 指向 null,但此时 GC 仍未发生,对象可能仍在内存中

那什么时候把 entry 加入 Reference queues 呢?是在 WeakReference 的父类 Reference 做的
*/
private void expungeStaleEntries() {
Entry<K,V> e;
	while ( (e = (Entry<K,V>) queue.poll()) != null) {
		int h = e.hash;
		int i = indexFor(h, 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;  // Help GC
				e.value = null; //  "   "
				size--;
				break;
			}
			prev = p;
			p = next;
		}
	}
}

注意到 expungeStaleEntries 这个方法被 size() 和 getTable() 调用,而 getTable() 又被 get/put/resize/remove 等方法调用
因此, WeakHashMap 并不是自动的删除  entry,只有当你调用了上述方法时,才会删除 entry

这通常不会引起什么问题,WeakHashMap 创建后就不再执行 size/get/put/resize/remove 这些方法的情况并不多


参考文章:
http://en.wikipedia.org/wiki/Weak_reference
https://www.ibm.com/developerworks/java/library/j-jtp11225/
https://weblogs.java.net/blog/2006/05/04/understanding-weak-references
http://www.iteye.com/topic/587995
http://www.jb51.net/article/36948.htm

你可能感兴趣的:(java,弱引用)