Strong Reference, Soft Reference, Weak Reference, Phantom Reference以及垃圾回收

有关这个话题的东东在网上一搜一大把,或许是网上讲的并不是很全,不是自己所需要的,再加上想让自己[size=medium][/size]有点理性认识。因此自己试验一把,记录下来以备后用。

Strong Reference — 强引用就不用说了,基本上写代码时引用某个对象是都用的是强引用。
Soft Reference – 软引用JDK中描述确保在抛出“OutOfMemory”前被回收。主要用于内存敏感型的缓存。但是我在后面的例子很困惑,为什么没有被回收,至今还没有答案。
Weak Reference – 弱引用主要被用于“规范化”的映射。这个“规范化”让我很纳闷。
Phantom Reference – 虚引用主要用于跟踪对象被回收的状态,一般用不到,也不去深究了。

下面主要已代码形式展开,包括自己写个几个测试代码以及几个实际在应用的源代码。
先来对Weak Reference和Soft Reference有个大致了解。

class A {
private static A a;
public void finalize(){
System.out.println("A OUT!");
}
}
public static void testWeakReference(){
ReferenceQueue<A> rq = new ReferenceQueue<A>();
WeakReference<A> a = new WeakReference<A>(new A(), rq);

System.gc();
System.out.println("Immediately after GC!");
Assert.isNull(a.get());
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("After Sleep!");

Assert.notNull(rq.poll());
}


调用testWeakReference,在jdk1.7环境下输出:
Immediately after GC!
Before Sleep!
A OUT!
After Sleep!
的概率稍大一些,不过多运行几次。“A OUT!”也会在出现在第一或者第二位。这就是为什么在其中放置Thread.sleep(1)让其sleep 1毫秒的原因。在显示调用System.gc()进行垃圾回收后,WeakReference对对象A的弱引用马上被清除,但是这时有可能还未加入到Reference Queue中,这跟JDK中描述的基本一致。在JDK1.7环境下,运行多次都不需要sleep,但在JDK1.6环境下sleep是必须的,它有延迟放入ReferenceQueue的情况。
如果把A类定义改成这样会如何?


class A {
private static A a;
public void finalize(){
System.out.println("A OUT!");
a = this;
}
}

这里用了对象逃生,这些并不会对testWeakReference有影响,只是通过A.a还可以得到该实例。但是该对象再次被垃圾回收时(比如赋值A.a = null;)不会在执行finalize方法。
这里有张状态转换图供大家参考(http://www.iteye.com/topic/484934),这里不多做解释。
[img]http://dl.iteye.com/upload/attachment/0071/8274/456ef1cb-9de6-3434-8188-94bd8663d32a.png[/img]

接下来认识一下SoftReference:

class Large {
private static int count = 0;
private int index;
private char[] bulk;

public Large(){
bulk = new char[20480000];
index = count++;
}

public void finalize(){
System.out.println("Large out!");
}
}
public static void testSoftReference(){
ReferenceQueue<Large> rq = new ReferenceQueue<Large>();
ReferenceQueue<A> rq2 = new ReferenceQueue<A>();
SoftReference<A> a = new SoftReference<A>(new A(), rq2);

List<SoftReference<Large>> list = new ArrayList<SoftReference<Large>>();
for(int i = 0; i < 10; i++){
list.add(new SoftReference<Large>(new Large(), rq));
}
}

这里让我产生一个很大的困惑,至今不解。这里会抛出“OutOfMemory”异常。输出:
4 Large out!
3 Large out!
2 Large out!
1 Large out!
0 Large out!
A OUT!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

在new到第6个new SoftReference<Large>(new Large(), rq)时报异常,如果单从输出可以看到前5个Large包括A都已经回收。但在运行时添加参数:
-XX:+HeapDumpOnOutOfMemoryError
在heapdump中可以看到有6个Large实例,和一个A实例都没有完全回收。并且还有一个现象只有第一个Large实例的bulk被回收了为null,其余都还在。
但是如果将Large中的finalize方法去掉,则不会有异常抛出。
在http://www.fasterj.com/articles/finalizer1.shtml找到了相关资料,但是还不能解释软引用在重写finalize方法后抛出“OutOfMemory”异常的情况。(后面会将这篇资料翻译一下)

暂时先把这个问题放到一边。对SoftReference的应用场景还是明确的,用于内存敏感型的缓存。Java.lang.Class中对字段,方法就是用SoftReference进行缓存的。另外一个不解的地方是JDK中对WeakReference应用场景的建议,用于“规范化”的映射。这里采用百度百科中的解析:弱引用适用于实现无法防止其键(或值)被回收的规范化映射。
再来剖析一下使用WeakReference来实现WeakHashMap,可能会对WeakReference有进一步的理解。借用JDK中对WeakHashMap的定义:
Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use.
基于哈希表的Map接口实现,其中的key都为弱引用。当key不能在正常使用时(被GC垃圾回收后),WeakHashMap中的该key对应的entry会被自动回收。
这里有几个关键词:
1、key为弱引用
2、key被回收后,对应的entry自动回收
WeakHashMap与HashMap的实现基本一致,以下会分析WeakHashMap与HashMap实现的两个主要不同之处,恰恰是这两个不同之处实现了上述两个关键词。
第一个不同实现第一个关键词,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;
}

......
}

WeakHashMap中的Entry实现继承了WeakReference,这还不够。在Entry构建函数中,super(key, queue);才真正把key作为弱引用。
无论是WeakHashMap还是HashMap都是以Entry形式放置在table数组中,那么如果Entry中的key被回收后,Entry中的其他元素还是存在的,有hash、next和value。因此需要从table表中剔除该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;
}
}
}
}


注释已经很明了,该函数就是用来从table表中剔除相应Entry的。当key被回收后,会放入WeakHashMap的ReferenceQueue(queue)中。这在put函数中可以验证:


public V put(K key, V value) {
Object k = maskNull(key);
int h = HashMap.hash(k.hashCode());
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);

for (Entry<K,V> 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<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e);
if (++size >= threshold)
resize(tab.length * 2);
return null;
}


首先判断该key是否已经存在,如果不存在,则新创建一个Entry。Entry中引入的就是WeakHashMap中的queue。
因此在expungeStaleEntries中,当key被回收后,queue.poll()获取对应Entry引用。根据还未被回收的hash值找到table表中对应的Entry。如果有相同的hash的Entry存在,则从该Entry链表中剔除该Entry,否则直接从table表中剔除。这样就实现了key被回收后,自动回收对应的Entry。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值