举个栗子
关于Reference对象,java.lang.ref包的文档说了一堆,跑起来看看才是王道,
public class Foo {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize#" + this);
super.finalize();
}
}
private static ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();
static {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Reference ref = null;
try {
ref = queue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("removed#" + ref);
}
}
}).start();
}
public static void main(String[] args) throws Exception{
PhantomReference<Foo> pr = pr(); // phantom reachable
WeakReference<Foo> wr = wr(); // weakly reachable
SoftReference<Foo> sr = sr(); // softly reachable
System.gc();
Thread.sleep(2000);
System.out.println("pr.isEnqueued()#"+pr.isEnqueued());
System.out.println("wr.get()#"+wr.get());
System.out.println("sr.get()#"+sr.get());
}
private static PhantomReference<Foo> pr() {
Foo foo = new Foo();
PhantomReference<Foo> pr = new PhantomReference<Foo>(foo, queue);
return pr;
}
private static WeakReference<Foo> wr() {
Foo foo = new Foo();
WeakReference<Foo> wr = new WeakReference<Foo>(foo, queue);
return wr;
}
private static SoftReference<Foo> sr() {
Foo foo = new Foo();
SoftReference<Foo> sr = new SoftReference<Foo>(foo, queue);
return sr;
}
输出是这样的,
removed#java.lang.ref.WeakReference@d48785
finalize#[email protected]
finalize#me.kisimple.just4fun.Foo@fdd15b
pr.isEnqueued()#false
wr.get()#null
sr.get()#me.kisimple.just4fun.Foo@a0fbd6
有时候是这样的,
finalize#[email protected]
finalize#[email protected]
removed#[email protected]
pr.isEnqueued()#false
wr.get()#null
sr.get()#[email protected]
finalize#
跟removed#
的顺序并不固定。
下面我们就根据这个输出来分析下当
the reachability of the referent has changed to the value corresponding to the type of the reference
时,也就是注释中的xxx reachable,referent的回收和reference入队列这两个操作。
finalize
- SoftReference,并不会被回收。SoftReference会尽可能长时间的保存在内存当中,只要不会OOM:)
- WeakReference,会被回收,finalize已执行;
- PhantomReference,会被回收,finalize已执行;
enqueue
- SoftReference,并没有入队列;
- WeakReference,会入队列,入队列的时机与finalize的执行顺序并不固定;
- PhantomReference,也没有入队列;
但是API文档中说道,
Some time after the garbage collector determines that the reachability of the referent has changed to the value corresponding to the type of the reference, it will add the reference to the associated queue.
但从上面的栗子只看到WeakReference的行为符合文档的说明,是有bug还是栗子有问题?
内存泄漏
关于WeakHashMap,API文档说明如下,
More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map implementations.
也就是说,即使WeakHashMap持有了key对象也不会阻止这个key对象被回收,也就相当于说,WeakHashMap只是持有了该key对象的一个WeakReference而已。当key对象被回收之后,相应的mapping也会被回收。来看下面的栗子,
private static class Foo {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize#" + this);
super.finalize();
}
}
private static class Bar {
private long id;
public Bar(long id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Bar ?
((Bar)obj).id == this.id : false;
}
@Override
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
}
private static Map<Bar, Object> sm = new HashMap<Bar, Object>();
private static Map<Bar, Object> wm = new WeakHashMap<Bar, Object>();
public static void main(String[] args) throws Exception{
strongMap();
weakMap();
System.gc();
Thread.sleep(2000);
System.out.println(sm.get(new Bar(27)));
System.out.println(wm.get(new Bar(127)));
}
private static void strongMap() {
Foo foo = new Foo();
sm.put(new Bar(27), foo);
}
private static void weakMap() {
Foo foo = new Foo();
wm.put(new Bar(127), foo);
}
输出如下,WeakHashMap持有的mapping妥妥的被回收了,而普通的HashMap则不会。
me.kisimple.just4fun.Main$Foo@2dce0
null
依靠这个特性WeakHashMap可以代替HashMap来防止一些内存泄漏,具体可参考这篇文档。
WeakHashMap
那么WeakHashMap到底是怎么实现的?RTFSC,
/**
* 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.key = key;这么一行了
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) {...}
public int hashCode() {...}
public String toString() {...}
}
WeakHashMap与普通Map最大的区别就在于这个Entry,它继承了WeakReference,并且在它的构造函数中有这么一句,super(key, queue);
,所以正如上面我们所说的,WeakHashMap只持有了一个key对象的弱引用,后面只要去查询引用队列queue
就可以知道这个key对象是不是已经被回收,如果被回收就将该Entry也清除掉,相应代码在expungeStaleEntries
方法中,
/**
* 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;
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;
// 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;
}
}
}
}
大部分对WeakHashMap的操作都会先去调用getTable
方法,而该方法会先去执行expungeStaleEntries
方法来执行上述的清理。
/**
* Returns the table after first expunging stale entries.
*/
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
这样也就不需要去启动一个线程专门来轮询引用队列了。
参考资料
- http://docs.oracle.com/javase/8/docs/api/java/lang/ref/package-summary.html
- https://weblogs.java.net/blog/2006/05/04/understanding-weak-references
- Plugging memory leaks with weak references
- Plugging memory leaks with soft references