Java中WeakReference、WeakHashMap、SoftReference、ReferenceQueue的作用和使用场景

时间:2022-10-30 19:14:30
 详见以下测试代码及其注释:
package test;

import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;

import org.junit.Test;

class VeryBig {
private static final intSIZE= 1000;
private double[]la= new double[SIZE];
private Stringident;

public VeryBig(String id) {
ident = id;
}

public VeryBig(String id, int s) {
ident = id;
la = new double[s];
}

public String toString() {
return ident;
}

protected void finalize() {
System.out.println("Finalizing " + ident);
}
}

public class ReferencesTest {
/**
* Strong Reference
* 是 Java 的默认引用实现, 它会尽可能长时间的存活于 JVM 内, 当没有任何强引用指向对象时, GC 执行后将会回收对象
*/
@Test
public void strongReferenceNotGC() {
System.out.println("start test strongReferenceNotGC");
Object referent = new VeryBig("strongReferenceNotGC");

/**
* 通过赋值创建 StrongReference
*/
Object strongReference = referent;

assertSame(referent, strongReference);

referent = null;
System.gc();

/**
* 由于对象还存在引用,因此gc后,对象未被回收
*/
assertNotNull(strongReference);
System.out.println("end test strongReferenceNotGC");
}

@Test
public void strongReferenceGC() throws InterruptedException {
System.out.println("start test strongReferenceGC");
Object referent = new VeryBig("strongReferenceGC");
referent = null;
System.gc();
Thread.sleep(500);
System.out.println("end test strongReferenceGC");
}

/**
* WeakReference<br>
* 顾名思义, 是一个弱引用, 当所引用的对象在 JVM 内不再有强引用时, GC后,弱引用被置成null,并回收所指的对象
*
* @throws InterruptedException
*/
@Test
public void weakReference() throws InterruptedException {
System.out.println("start test weakReference");
WeakReference<Object> weakRerference = new WeakReference<Object>(new VeryBig(
"weakReference"));
System.gc();
Thread.sleep(500);
/**
*weak reference</strong> 在 GC 后会被置成null,对象就可以被 回收了。。。
*/
assertNull(weakRerference.get());
System.out.println("end test weakReference");
}

/**
* WeakHashMap<br>
* 使用 WeakReference 作为 key, 一旦没有指向 key 的强引用, WeakHashMap 在 GC 后将自动删除相关的
* entry
*
* @throws InterruptedException
*/
@Test
public void weakHashMap() throws InterruptedException {
Map<Object, Object> weakHashMap = new WeakHashMap<Object, Object>();
Object key = new VeryBig("weakHashMap key");
Object value = new Object();
weakHashMap.put(key, value);

assertTrue(weakHashMap.containsValue(value));

key = null;
System.gc();
/**
* 等待无效 entries 进入 ReferenceQueue 以便下一次调用 getTable 时被清理
*/
Thread.sleep(1000);

/**
* 一旦没有指向 key 的强引用, WeakHashMap 在 GC 后将自动删除相关的 entry
*/
assertFalse(weakHashMap.containsValue(value));
}

/**
* SoftReference<br>
* 与 WeakReference 的特性基本一致, 最大的区别在于
* SoftReference会尽可能长的保留引用,不会在GC时就回收对象,而是直到 JVM 内存不足时才会被回收(虚拟机保证),
* 这一特性使得 SoftReference 非常适合缓存应用
*/
@Test
public void softReference() {
SoftReference<Object> softRerference = new SoftReference<Object>(new VeryBig(
"softReference"));

assertNotNull(softRerference.get());

System.gc();

/**
* soft references 只有在 jvm OutOfMemory 之前才会被回收, 所以它非常适合缓存应用
*/
assertNotNull(softRerference.get());

// make oom....
int i = 0;
while (true) {
try {
++i;
new VeryBig("oom ", 10000000);
} catch (Throwable e) {
System.out.println("OOM after " + i + " times");
e.printStackTrace();
break;
}
}

assertNull(softRerference.get());
}

/**
* PhantomReference<br>
* Phantom Reference(幽灵引用) 与 WeakReference 和 SoftReference 有很大的不同, 因为它的
* get() 方法永远返回 null, 这也正是它名字的由来
*/
@Test
public void phantomReferenceAlwaysNull() {
ReferenceQueue<Object> q = new ReferenceQueue<Object>();
PhantomReference<Object> phantomReference = new PhantomReference<Object>(
new VeryBig("phantomReferenceAlwaysNull"), q);

/**
* phantom reference的 get 方法永远返回 null
*/
assertNull(phantomReference.get());

assertNull(q.poll());
System.gc();
assertNull(q.poll());
}

/**
* RererenceQueue<br>
* 当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收, 这时可以做一些合适的清理工作. 将一个
* ReferenceQueue 传给一个 Reference 的构造函数, 当对象被回收时, 虚拟机会自动将这个weak</strong> ref插入到
* ReferenceQueue 中, WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的
* entries
*
* @throws InterruptedException
*/
@Test
public void referenceQueueWithWeakReference() throws InterruptedException {
Object referent = new VeryBig("referenceQueueWithWeakReference");
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
Reference<Object> ref = new WeakReference<Object>(referent, referenceQueue);

assertFalse(ref.isEnqueued());
Reference<? extends Object> polled = referenceQueue.poll();
assertNull(polled);

referent = null;
System.gc();

assertTrue(ref.isEnqueued());
Reference<? extends Object> removed = referenceQueue.remove();
assertNotNull(removed);
assertSame(ref, removed);
assertNull(removed.get());
}

@Test
public void referenceQueueWithSoftReference() throws InterruptedException {
Object referent = new VeryBig("referenceQueueWithWeakReference");
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
Reference<Object> ref = new SoftReference<Object>(referent, referenceQueue);

assertFalse(ref.isEnqueued());
Reference<? extends Object> polled = referenceQueue.poll();
assertNull(polled);

referent = null;
// make oom....
try {
new VeryBig("oom ", 100000000);
} catch (Throwable e) {

}
assertTrue(ref.isEnqueued());
Reference<? extends Object> removed = referenceQueue.remove();
assertNotNull(removed);
assertSame(ref, removed);
assertNull(removed.get());
}
}


参考:

http://www.iteye.com/topic/401478

http://zhang-xzhi-xjtu.iteye.com/blog/484934

http://zhang-xzhi-xjtu.iteye.com/blog/413159


另外: 关于WeakReference的理解。以下内容供参考。


java语言中为对象的引用分为了四个级别,分别为 强引用 、软引用、弱引用、虚引用。

本文只针对java中的弱引用进行一些分析,如有出入还请多指正。

在分析弱引用之前,先阐述一个概念:什么是对象可到达和对象不可到达状态。

其实很简单,我举个例子:

现在有如下两个类class A class B,在JVM上生成他们两个类的实例分别为 instance a  instance b

有如下表达式:

A a = new A();

B b = new B();

两个强引用对象就生成了,好吧,那么这个时候我做一下修改:

A a = new A();

B b = new B(a);

B的默认构造函数上是需要一个A的实例作为参数的,那么这个时候 A和B就产生了依赖,也可以说a和b产生了依赖,我们再用一个接近内存结构的图来表达:

 

Java中WeakReference、WeakHashMap、SoftReference、ReferenceQueue的作用和使用场景

a是对象A的引用,b是对象B的引用,对象B同时还依赖对象A,那么这个时候我们认为从对象B是可以到达对象A的。

于是我又修改了一下代码

A a = new A();

B b = new B(a);

a = null;

A对象的引用a置空了,a不再指向对象A的地址,我们都知道当一个对象不再被其他对象引用的时候,是会被GC回收的,很显然及时a=null,那么A对象也是不可能被回收的,因为B依然依赖与A,在这个时候,造成了内存泄漏!

那么如何避免上面的例子中内存泄漏呢?

很简单:

A a = new A();

B b = new B(a);

a = null;

b = null;

这个时候B对象再也没有被任何引用,A对象只被B对象引用,尽管这样,GC也是可以同时回收他们俩的,因为他们处于不可到达区域。

 

弱引用来了!

A a = new A();

WeakReference wr = new WeakReference(a);

//B b = new B(a);

 

Java中WeakReference、WeakHashMap、SoftReference、ReferenceQueue的作用和使用场景

当 a=null ,这个时候A只被弱引用依赖,那么GC会立刻回收A这个对象,这就是弱引用的好处!他可以在你对对象结构和拓扑不是很清晰的情况下,帮助你合理的释放对象,造成不必要的内存泄漏!!