Java软引用、弱引用和虚引用

介绍

在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。
从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。
这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。


强引用(StrongReference)

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

软引用(SoftReference)

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列(ReferenceQueue)中。软引用是在垃圾回收之后,才加入队列。

弱引用(WeakReference)

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现只具有弱引用的对象,不管当前内存空间足够与否都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列(ReferenceQueue)中。弱引用是在垃圾回收之后,才加入队列。

虚引用(PhantomReference)

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
程序可以通过判断引用队列中是否已经加入虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
在任何时候,我们都可以调用ReferenceQueue#poll()来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。

垃圾回收

在垃圾回收时,软引用和弱引用差别不大,JVM都是先把SoftReferenceWeakReference#referent字段值设置成null,referent可以认为对象销毁的标志,也就是referent=null时,之后加入到引用队列(我们可以认为JVM先回收堆对象占用的内存,然后才将软引用或弱引用加入到引用队列。这里回收是在对象不会再生的情况下,因为对象可能在finalize()中再生);而虚引用则不同,如果某个堆中的对象,只有虚引用,那么JVM会将PhantomReference加入到引用队列中,JVM不会自动将referent字段值设置成null(先保留堆对象的内存空间)。
referentjava.lang.ref.Reference类的私有字段,虽然没有暴露出共有API来访问这个字段,但是我们可以通过反射拿到这个字段的值,这样就能知道引用被加入到引用队列的时候,referent到底是不是null。SoftReferenceWeakReference是一样的,这里我们以WeakReference为例。

例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;

// 会报空指针:WeakReference中的referent被设置成null,之后加入到ReferenceQueue
public class TestWeakReference {
private static volatile boolean isRun = true;
private static volatile ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();

public static void main(String[] args) throws Exception {
String abc = new String("abc");
System.out.println(abc.getClass() + "@" + abc.hashCode());

new Thread() {
public void run() {
while (isRun) {
Object o = referenceQueue.poll();
if (o != null) {
try {
Field rereferent = Reference.class.getDeclaredField("referent");
rereferent.setAccessible(true);
Object result = rereferent.get(o);
//说明result是null
System.out.println("gc will collect:" + result.getClass() + "@" + result.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}.start();

// 对象是弱可达的
WeakReference<String> weak = new WeakReference<String>(abc, referenceQueue);
System.out.println("weak=" + weak);

// 清除强引用,触发GC
abc = null;
System.gc();
//等待GC被回收
Thread.sleep(3000);
isRun = false;
}
}

控制台

1
2
3
4
class java.lang.String@96354
weak=java.lang.ref.WeakReference@54624a40
java.lang.NullPointerException
at TestWeakReference$1.run(TestWeakReference.java:25)

结论:当我们清除强引用,触发GC的时候,JVM检测到new String("abc")这个堆中的对象只有WeakReference,那么JVM会释放堆对象的内存,并自动将WeakReference#referent字段设置成null,所以result.getClass()会报空指针异常。
换成PhantomReference控制台打印

1
2
3
class java.lang.String@96354
weak=java.lang.ref.PhantomReference@54624a40
gc will collect:class java.lang.String@96354

结论:当PhantomReference加入到引用队列的时候,referent字段的值并不是null,而且堆对象占用的内存空间仍然存在。也就是说对于虚引用,JVM是先将其加入引用队列(然后才删除对象),当我们从引用队列删除PhantomReference对象之后(此时堆中的对象是unreachable的),那么JVM才会释放堆对象占用的内存空间。由此可见,使用虚引用有潜在的内存泄露风险,因为JVM不会自动帮助我们释放,我们必须要保证它指向的堆对象是不可达的。(这里有个问题,如果sleep(3000),GC并没有回收它,因为是回收之前加入队列,如果没有回收它,自然会存在对象)所有的 PhantomReference对象都必须用经过关联的ReferenceQueue来创建。

例子2

验证软引用,弱引用,虚引用进入队列是不是需要执行finalize()

1
2
3
4
5
6
7
8
9
10
11
package demo6;

import java.util.Date;

class B {

@Override
protected void finalize() throws Throwable {
System.out.println("finalize at " + new Date());
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package demo6;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.Date;

public class Tem {

public static void main(String[] args) throws Exception {

ReferenceQueue queue = new ReferenceQueue();
//SoftReference ref = new SoftReference(new B(), queue);
//WeakReference ref = new WeakReference(new B(), queue);
PhantomReference ref = new PhantomReference(new B(), queue);

while (true) {
Object obj = queue.poll();
if (obj != null) {
System.out.println("queue.poll at " + new Date() + " " + obj);
break;
}
System.gc();
System.out.println("run once.");
}
Thread.sleep(100000);
}
}

结果:WeakReference不是必须进入finalize()才能进入队列。PhantomReference则必须进入finalize()才能进入队列。但这并不能说明对象销毁和队列的顺序。

综合例子1和例子2

WeakReference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package demo1;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.Date;


public class Tem {

public static void main(String[] args) throws Exception {
B b=new B();
ReferenceQueue queue = new ReferenceQueue();
//SoftReference ref1 = new SoftReference(new B(), queue);
WeakReference ref2 = new WeakReference(b, queue);
//PhantomReference ref3 = new PhantomReference(b, queue);

b=null;
while (true) {
Object obj = queue.poll();
if (obj != null) {
System.out.println("queue.poll at " + new Date() + " " + obj);

try {
Field rereferent = Reference.class.getDeclaredField("referent");
rereferent.setAccessible(true);
Object result = rereferent.get(obj);
//说明result是null
System.out.println("gc will collect:" + result.getClass() + "@" + result.hashCode());
} catch (Exception e) {
e.printStackTrace();
}

break;
}

System.gc();
System.out.println("run once.");
}
Thread.sleep(100000);
}
}

弱引用,finalize()和引用队列不是一个线程,没有必须进入finalize(),才进入引用队列的条件。进入引用队列前,referent已经是null。

1
2
3
4
5
run once.
finalize at Mon Sep 26 17:10:13 CST 2016
queue.poll at Mon Sep 26 17:10:13 CST 2016 java.lang.ref.WeakReference@8c9dee3
java.lang.NullPointerException
at demo1.Tem.main(Tem.java:33)

PhantomReference
虚引用,必须进入finalize()才能进入队列,进入队列referent还是有值存在。(回收对象之前,加入队列)

1
2
3
4
5
6
7
run once.
run once.
run once.
finalize at Mon Sep 26 17:33:09 CST 2016
run once.
queue.poll at Mon Sep 26 17:33:11 CST 2016 java.lang.ref.PhantomReference@4ec8c719
gc will collect:class demo1.B@1591599764

对象再生和引用队列问题

1
2
3
4
5
6
7
8
9
10
11
12
13
package demo2;

public class A {
static A a;

public A(){
}

@Override
public void finalize() {
a = this;
}
}

WeakReference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package demo2;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
*
* 弱引用对象再生,依然进入引用队列
*
*/
public class M {
public static void main(String[] args) throws InterruptedException {
A a=new A();
System.out.println(a.hashCode());
ReferenceQueue queue = new ReferenceQueue();
WeakReference ref = new WeakReference(a, queue);
System.out.println(ref.get());

a=null;

Object obj = null;
obj = queue.poll();
System.out.println("1="+obj);

System.gc();
Thread.sleep(10000);
System.gc();

System.out.println("2="+ref.get());
obj = queue.poll();

//弱引用队列有值,说明对象已经被销毁
//但是A对象又在finalize里再生
//所以不能用队列来控制软引用,弱引用对象的生命周期
//执行GC的时候,A对象状态变成finalized,进入队列,至于对象再生,则状态不能再改变
//因为,进入队列和finalize()是2个不同的线程。可能先进入队列之后,才进入finalize()
System.out.println("3="+obj);
System.out.println(A.a.hashCode());
}
}

1
2
3
4
5
6
366712642
test.A@15db9742
1=null
2=null
3=java.lang.ref.WeakReference@6d06d69c
366712642

弱引用队列有值,说明对象已经被销毁,但是A对象又在finalize里再生,所以不能用队列来控制软引用,弱引用对象的生命周期,执行GC的时候,A对象状态变成finalized,进入队列,至于对象再生,则状态不能再改变。因为进入队列和finalize()是2个不同的线程。可能先进入队列之后,才进入finalize(),对象的状态来不及改变。

1
2
3
4
5
6
1022736404
demo2.A@3cf5b814
1=null
2=null
3=java.lang.ref.WeakReference@773de2bd
1022736404

PhantomReference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package demo2;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;


/**
*
* 虚引用再生问题,finalize和进入队列顺序问题
*
*/
public class W {

public static void main(String[] args) throws InterruptedException {
ReferenceQueue queue = new ReferenceQueue();
PhantomReference ref = new PhantomReference(new A(), queue);

System.out.println(ref.get());
Object obj = null;
obj = queue.poll();
System.out.println("1=" + obj);

// 第一次gc
System.gc();
Thread.sleep(10000);
System.gc();

System.out.println("2="+ref.get());
obj = queue.poll();
System.out.println("3="+obj); //第1次gc,对象再生,没有加入队列。
//虚引用是必须进入finalize方法才能进入队列,如果对象在此处再生,就不会进入队列。

A.a = null;

// 第二次gc
System.gc();
Thread.sleep(10000);
obj = queue.poll(); //第2次gc,再生对象=null,加入队列。
System.out.println(obj);
}
}
//虚引用更容易控制对象生命周期

/*null
1=null
2=null
3=null
java.lang.ref.PhantomReference@1e638ee4
*/

虚引用是必须进入finalize()才能进入队列,如果对象在此处再生,就不会进入队列,所以队列里为null。

1
2
3
4
5
null
1=null
2=null
3=null
java.lang.ref.PhantomReference@1e638ee4

示例

示例1


图画的有点问题,str1是指向字符串,并不是指向weak1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class ReferenceDemo {

public static void main(String[] args) {
// 引用队列
ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();
String str = new String("字符串");
// 弱引用
WeakReference<String> weakReference1 = new WeakReference<String>(str, referenceQueue);
WeakReference<String> weakReference2 = new WeakReference<String>(str, referenceQueue);
// 强引用str没有指向字符串
str = null;
// 弱引用指向的字符串,此时指向str1;str1是个强引用
// 如果这里没有强引用,弱引用就会在Gc执行之后被消除
String str1 = weakReference1.get();
// System.out.println("弱引用:" + str1);
System.out.println("引用队列里的引用:" + referenceQueue.poll());
// 执行GC回收
System.out.println("------------------------------------GC回收----------------------------------------------");
for (int i = 0; i < 100; i++) {
System.gc();
}
// 此时弱引用没有被消除,因为str1是强引用
str1 = weakReference1.get();
System.out.println("弱引用1:" + weakReference1.get());
System.out.println("弱引用2:" + weakReference2.get());
System.out.println(str1);
// 引用队列添加被回收的对象
System.out.println("引用队列:" + referenceQueue.poll());
}
}

1
2
3
4
5
6
引用队列里的引用:null
------------------------------------GC回收----------------------------------------------
弱引用1:字符串
弱引用2:字符串
字符串
引用队列:null

结论: 但是str=null,str1=weakReference1.get()str1指向字符串,并且weak1weak2同时有字符串的引用,“字符串”没有销毁,所以weak1weak2都是强引用。

示例2

创建一个String对象、ReferenceQueue对象和WeakReference对象。

1
2
3
4
5
6
// 创建一个强引用
String str = new String("hello");
// 创建引用队列;表明队列中存放String对象的引用
ReferenceQueue rq = new ReferenceQueue();
// 创建一个弱引用,它引用"hello"对象,并且与rq引用队列关联
WeakReference wf = new WeakReference(str, rq);


在图中,带实线的箭头表示强引用,带虚线的箭头表示弱引用。从图中可以看出,此时”hello”对象被str强引用,并且被一个WeakReference对象弱引用,因此”hello”对象不会被垃圾回收。

示例3

把引用”hello”对象的str变量置为null,然后再通过WeakReference弱引用的get()获得”hello”对象的引用。

1
2
3
4
5
6
7
String str = new String("hello"); //1  
ReferenceQueue rq = new ReferenceQueue(); //2
WeakReference wf = new WeakReference(str, rq); //3
str=null; //4 取消"hello"对象的强引用
String str1=wf.get().toString(); //5 假如"hello"对象没有被回收,str1引用"hello"对象
//假如"hello"对象没有被回收,rq.poll()返回null
Reference ref=rq.poll(); //6


“hello”对象只具有弱引用
执行完以上标记4后,内存中引用与对象的关系如图所示,此时”hello”对象仅仅具有弱引用,因此它有可能被垃圾回收。假如它还没有被垃圾回收,那么接下来在标记5执行wf.get()会返回”hello”对象的引用,并且使得这个对象被str1强引用。再接下来在标记6执行rq.poll()会返回null,因为此时引用队列中没有任何引用。ReferenceQueue#poll()用于返回队列中的引用,如果没有则返回null。

示例4

1
2
3
4
5
6
7
8
9
String str = new String("hello"); //1
ReferenceQueue rq = new ReferenceQueue(); // 2
WeakReference wf = new WeakReference(str, rq); // 3
str = null; // 4
// 两次催促垃圾回收器工作,提高"hello"对象被回收的可能性
System.gc(); // 5
System.gc(); // 6
String str1 = wf.get().toString(); // 7 假如"hello"对象被回收,str1为null
Reference ref = rq.poll(); // 8


“hello”对象被垃圾回收,弱引用被加入到引用队列
在以下程序代码中,执行完标记4后,”hello”对象仅仅具有弱引用。接下来两次调用System.gc(),催促垃圾回收器工作,从而提高 “hello”对象被回收的可能性。假如”hello”对象被回收,那么WeakReference对象的引用被加入到ReferenceQueue中, 接下来wf.get()返回null,并且rq.poll()返回WeakReference对象的引用。图显示执行完标记8后内存中引用与对象的关系。

示例5

依次创建10个软引用、10个弱引用和10个虚引用,它们各自引用一个Grocery对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package all;

class Grocery {
private static final int SIZE = 10000;
// 属性d使得每个Grocery对象占用较多内存,有80K左右
private double[] d = new double[SIZE];
private String id;

public Grocery(String id) {
this.id = id;
}

public String toString() {
return id;
}

public void finalize() {
System.out.println("Finalizing " + id);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package all;

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.HashSet;
import java.util.Set;

public class All_10 {
@SuppressWarnings("rawtypes")
private static ReferenceQueue rq = new ReferenceQueue();

@SuppressWarnings("rawtypes")
public static void checkQueue() {
Reference inq = rq.poll();
// 从队列中取出一个引用
if (inq != null) {
System.out.println("In queue: " + inq + " : " + inq.get());
}
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) {
final int size = 10;
System.out.println("--------------------创建10个Grocery对象以及10个软引用-------------------------------");
// 创建10个Grocery对象以及10个软引用
Set sa = new HashSet();
for (int i = 0; i < size; i++) {
SoftReference ref = new SoftReference(new Grocery("soft-" + i), rq);
System.out.println("Just created soft: " + ref.get());
sa.add(ref);
}
System.gc();
checkQueue();
System.out.println("--------------------创建10个Grocery对象以及10个弱引用-------------------------------");
// 创建10个Grocery对象以及10个弱引用
Set wa = new HashSet();
for (int i = 0; i < size; i++) {
WeakReference ref = new WeakReference(new Grocery("weak-" + i), rq);
System.out.println("Just created weak: " + ref.get());
wa.add(ref);
}
System.gc();
checkQueue();
System.out.println("--------------------创建10个Grocery对象以及10个虚引用-------------------------------");
// 创建10个Grocery对象以及10个虚引用
Set pa = new HashSet();
for (int i = 0; i < size; i++) {
PhantomReference ref = new PhantomReference(new Grocery("Phantom-" + i), rq);
System.out.println("Just created Phantom: " + ref.get());//调用phanRef.get()不管在什么情况下会一直返回null,虚引用的特点
pa.add(ref);
}
System.gc();
checkQueue();
}
}

控制台打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
--------------------创建10Grocery对象以及10个软引用-------------------------------
Just created soft: soft-0
Just created soft: soft-1
Just created soft: soft-2
Just created soft: soft-3
Just created soft: soft-4
Just created soft: soft-5
Just created soft: soft-6
Just created soft: soft-7
Just created soft: soft-8
Just created soft: soft-9
--------------------创建10Grocery对象以及10个弱引用-------------------------------
Just created weak: weak-0
Just created weak: weak-1
Just created weak: weak-2
Just created weak: weak-3
Just created weak: weak-4
Just created weak: weak-5
Just created weak: weak-6
Just created weak: weak-7
Just created weak: weak-8
Just created weak: weak-9
--------------------创建10Grocery对象以及10个虚引用-------------------------------
Finalizing weak-3
Finalizing weak-2
Finalizing weak-1
Finalizing weak-0
Finalizing weak-5
Finalizing weak-4
Just created Phantom: null
Finalizing weak-9
Finalizing weak-8
Just created Phantom: null
Finalizing weak-7
Just created Phantom: null
Finalizing weak-6
Just created Phantom: null
Just created Phantom: null
Just created Phantom: null
Just created Phantom: null
Just created Phantom: null
Just created Phantom: null
Just created Phantom: null
In queue: java.lang.ref.WeakReference@13883d5f : null
Finalizing Phantom-4
Finalizing Phantom-6
Finalizing Phantom-7
Finalizing Phantom-8
Finalizing Phantom-3
Finalizing Phantom-5
Finalizing Phantom-9
Finalizing Phantom-0
Finalizing Phantom-1
Finalizing Phantom-2

结论:虚引用形同虚设,它所引用的对象随时可能被垃圾回收;具有弱引用的对象拥有稍微长的生命周期,当垃圾回收器执行回收操作时,有可能被垃圾回收;具有软引用的对象拥有较长的生命周期,但在Java虚拟机认为内存不足的情况下,也会被垃圾回收。

软引用

1
2
MyObject aRef=new MyObject();  
SoftReference aSoftRef=new SoftReference(aRef);

对于这个MyObject对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量aReference的强引用,所以这个MyObject对象是强可及对象。
Java虚拟机的垃圾收集线程对软可及对象和其他一般Java对象进行区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。
也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。
也就是说, 一旦SoftReference保存对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之后,get()将返回null。

软引用实现缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package soft;

public class Employee {
private String id;// 雇员的标识号码
private String name;// 雇员姓名

// 构造方法
public Employee(String id) {
this.id = id;
getDataFromlnfoCenter();
}

// 到数据库中取得雇员信息
private void getDataFromlnfoCenter() {
// 和数据库建立连接井查询该雇员的信息,将查询结果赋值
// 给name,department,plone,salary等变量
// 同时将origin赋值为"From DataBase"
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package soft;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;

public class EmployeeCache {
static private EmployeeCache cache;// 一个Cache实例
private Hashtable employeeRefs;// 用于Chche内容的存储
private ReferenceQueue q;// 垃圾Reference的队列

// 继承SoftReference,使得每一个实例都具有可识别的标识。
// 并且该标识与其在HashMap内的key相同。
private class EmployeeRef extends SoftReference {
private String _key = "";

public EmployeeRef(Employee em, ReferenceQueue q) {
super(em, q);
_key = em.getId();
}
}

// 构建一个缓存器实例
private EmployeeCache() {
employeeRefs = new Hashtable();
q = new ReferenceQueue();
}

// 取得缓存器实例
public static EmployeeCache getInstance() {
if (cache == null) {
cache = new EmployeeCache();
}
return cache;
}

// 以软引用的方式对一个Employee对象的实例进行引用并保存该引用
public void cacheEmployee(Employee em) {
cleanCache();// 清除垃圾引用
EmployeeRef ref = new EmployeeRef(em, q);
employeeRefs.put(em.getId(), ref);
}

// 依据所指定的ID号,重新获取相应Employee对象的实例
public Employee getEmployee(String ID) {
Employee em = null;
// 缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。
if (employeeRefs.containsKey(ID)) {
EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);
em = (Employee) ref.get();
}
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
// 并保存对这个新建实例的软引用
if (em == null) {
em = new Employee(ID);
System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);
this.cacheEmployee(em);
}
return em;
}

// 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象
private void cleanCache() {
EmployeeRef ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
employeeRefs.remove(ref._key);
}
}

// 清除Cache内的全部内容
public void clearCache() {
cleanCache();
employeeRefs.clear();
System.gc();
System.runFinalization();
}
}

弱引用

在Java集合中有一种特殊的Map类型WeakHashMap, 在这种Map中存放键对象的弱引用,当一个键对象被垃圾回收,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。
以下代码MapCache类的main()创建一个WeakHashMap对象,它存放一组Key对象的弱引用,此外main()还创建一个数组对象,它存放部分Key对象的强引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package weak;

import java.util.WeakHashMap;

class Key {
String id;

public Key(String id) {
this.id = id;
}

public String toString() {
return id;
}

public int hashCode() {
return id.hashCode();
}

public boolean equals(Object r) {
return (r instanceof Key) && id.equals(((Key) r).id);
}

public void finalize() {
System.out.println("Finalizing Key " + id);
}
}

class Value {
String id;

public Value(String id) {
this.id = id;
}

public String toString() {
return id;
}

public void finalize() {
System.out.println("Finalizing Value " + id);
}
}

public class MapCache {
public static void main(String[] args) throws Exception {
int size = 1000;// 或者从命令行获得size的大小
Key[] keys = new Key[size]; // 存放键对象的强引用
WeakHashMap whm = new WeakHashMap();
for (int i = 0; i < size; i++) {
Key k = new Key(Integer.toString(i));
Value v = new Value(Integer.toString(i));
if (i % 3 == 0) {
keys[i] = k; // 使Key对象持有强引用
}
whm.put(k, v); // 使Key对象持有弱引用
}
// 催促垃圾回收器工作
System.gc();// 把CPU让给垃圾回收器线程
Thread.sleep(8000);
}
}

控制台打印

1
2
3
4
5
6
7
Finalizing Key 587
Finalizing Key 985
Finalizing Key 998
Finalizing Key 997
Finalizing Key 995
Finalizing Key 994
..................

结果:当执行System.gc()后,垃圾回收器只会回收那些仅仅持有弱引用的Key对象。id可以被3整数的Key对象持有强引用,因此不会被回收。

# Java

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×