Java对象的强、软、弱和虚引用


  在JDK1.2以往的版本中,当一个对象不被任何变量 引用,那么程序就 无奈再 使用这个对象 。也便是说,惟独对象处于可 波及状态,程序 威力 使用它 。这 就像在日常生活中,从商店购买了某样物品后,如果有用,就向来保留它,不然就把它扔到垃圾箱,由清洁工人收走 。普通说来,如果物品已经被扔到垃圾箱,想再 把它捡回来 使用就不可能了 。

但有时候状况并不这么 方便,你可能会遇到 类似鸡肋一样的物品,食之无味,弃之惋惜 。这种物品现在已经无用了,保留它会占空间,然而 立即扔掉它也不划算,因 为 兴许 将来还会派用场 。关于这样的可有可无的物品,一种折衷的 解决 步骤是:如果家里空间足够,就先把它保留在家里,如果家里空间不够, 即便把家里全部的垃 圾 革除,还是 无奈 包容那些必不可少的生活用品,那么再扔掉这些可有可无的物品 。

从JDK1.2版本开始,把对象的 引用分为四种级别,从而使程序能更加灵便的操纵对象的生命周期 。这四种级别由高到低 顺次为:强 引用、软 引用、弱 引用和虚 引用 。

1.强 引用

  本章前文介绍的 引用实际上都是强 引用,这是 使用最 广泛的 引用 。如果一个对象 存在强 引用,那就 类似于必不可少的生活用品,垃圾回收器绝对不会回收它 。当内存空 间缺乏,Java 虚构机宁愿抛出OutOfMemoryError 舛误,使程序 异样终止,也不会靠 随便回收 存在强 引用的对象来解决内存缺乏问题 。

2.软 引用(SoftReference)

  如果一个对象只 存在软 引用,那就 类似于可有可物的生活用品 。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间缺乏了,就会回收这些对象的内存 。 惟独垃圾回收器没有回收它,该对象就 可以被程序 使用 。软 引用可用来实现内存敏感的高速缓存 。

软 引用 可以和一个 引用队列(ReferenceQueue)联合 使用,如果软 引用所 引用的对象被垃圾回收,Java 虚构机就会把这个软 引用加入到与之关联的 引用队列中 。

3.弱 引用(WeakReference)

  如果一个对象只 存在弱 引用,那就 类似于可有可物的生活用品 。弱 引用与软 引用的区别在于:只 存在弱 引用的对象 占有更短暂的生命周期 。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只 存在弱 引用的对象, 无论目前内存空间足够与否,都会回收它的内存 。不过,由于垃圾回收器是一个优先级很低的线程, 因此不 定然会很快发现那些只 存在弱 引用的对象 。

弱 引用 可以和一个 引用队列(ReferenceQueue)联合 使用,如果弱 引用所 引用的对象被垃圾回收,Java 虚构机就会把这个弱 引用加入到与之关联的 引用队列中 。

4.虚 引用(PhantomReference)

  "虚 引用"顾名思义,便是形同虚设,与 其余几种 引用都不同,虚 引用并不会决定对象的生命周期 。如果一个对象仅持有虚 引用,那么它就和没有任何 引用一样,在任何时候都可能被垃圾回收 。

虚 引用重要用来跟踪对象被垃圾回收的 运动 。虚 引用与软 引用和弱 引用的一个区别在于:虚 引用必须和 引用队列(ReferenceQueue)联合 使用 。当垃 圾回收器 预备回收一个对象时,如果发现它还有虚 引用,就会在回收对象的内存之前,把这个虚 引用加入到与之关联的 引用队列中 。程序 可以通过推断 引用队列中是 否已经加入了虚 引用,来了解

被 引用的对象是不是将要被垃圾回收 。程序如果发现某个虚 引用已经被加入到 引用队列,那么就 可以在所 引用的对象的内存被回收之前采取必要的行动 。

在本书中," 引用"既 可以作为动词,也 可以作为名词,读者应该依据上下文来 划分" 引用"的 含意 。

在java.lang.ref包中提供了三个类:SoftReference类、WeakReference类和PhantomReference类,它 们分别代表软 引用、弱 引用和虚 引用 。ReferenceQueue类 示意 引用队列,它 可以和这三种 引用类联合 使用,以便跟踪Java 虚构机回收所 引用的对 象的 运动 。以下程序 缔造了一个String对象、ReferenceQueue对象和WeakReference对象:

// 缔造一个强 引用

String str = new String("hello");

// 缔造 引用队列, <String>为范型标记,表明队列中 存放String对象的 引用

ReferenceQueue<String> rq = new ReferenceQueue<String>();

// 缔造一个弱 引用,它 引用"hello"对象,而且与rq 引用队列关联

//<String>为范型标记,表明WeakReference会弱 引用String对象

WeakReference<String> wf = new WeakReference<String>(str, rq);

以上程序代码执行 结束,内存中 引用与对象的关系如图11-10所示 。

Java对象的强、软、弱和虚

引用

图11-10 "hello"对象同时 存在强 引用和弱 引用

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

在以下程序代码中,把 引用"hello"对象的str变量置为null, 而后再通过WeakReference弱 引用的get() 步骤 获得"hello"对象的 引用:

String str = new String("hello"); //①

ReferenceQueue<String> rq = new ReferenceQueue<String>(); //②

WeakReference<String> wf = new WeakReference<String>(str, rq); //③

str=null; //④ 取缔"hello"对象的强 引用

String str1=wf.get(); //⑤如果"hello"对象没有被回收,str1 引用"hello"对象

//如果"hello"对象没有被回收,rq.poll()返回null

Reference<? extends String> ref=rq.poll(); //⑥

执行完以上第④行后,内存中 引用与对象的关系如图11-11所示,此 时"hello"对象仅仅 存在弱 引用, 因此它有可能被垃圾回收 。如果它还没有被垃圾回收,那么接下来在第⑤行执行wf.get() 步骤会返回 "hello"对象的 引用,而且使得这个对象被str1强 引用 。再接下来在第⑥行执行rq.poll() 步骤会返回null,由于此时 引用队列中没有任何 引用 。ReferenceQueue的poll() 步骤用于返回队列中的 引用,如果没有则返回null 。

Java对象的强、软、弱和虚

引用

图11-11 "hello"对象只 存在弱 引用

在以下程序代码中,执行完第④行后,"hello"对象仅仅 存在弱 引用 。接下来两次调用System.gc() 步骤,督促垃圾回收器工作,从而 普及 "hello"对象被回收的可能性 。如果"hello"对象被回收,那么WeakReference对象的 引用被加入到ReferenceQueue中, 接下来wf.get() 步骤返回null,而且rq.poll() 步骤返回WeakReference对象的 引用 。图11-12显示了执行完第⑧行后内存 中 引用与对象的关系 。

String str = new String("hello"); //①

ReferenceQueue<String> rq = new ReferenceQueue<String>(); //②

WeakReference<String> wf = new WeakReference<String>(str, rq); //③

str=null; //④

//两次督促垃圾回收器工作, 普及"hello"对象被回收的可能性

System.gc(); //⑤

System.gc(); //⑥

String str1=wf.get(); //⑦ 如果"hello"对象被回收,str1为null

Reference<? extends String> ref=rq.poll(); //⑧

Java对象的强、软、弱和虚

引用

图11-12 "hello"对象被垃圾回收,弱 引用被加入到 引用队列

在以下例程11-15的References类中, 顺次 缔造了10个软 引用、10个弱 引用和10个虚 引用,它们各自 引用一个Grocery对象 。从程序运 行时的打印 后果 可以看出,虚 引用形同虚设,它所 引用的对象随时可能被垃圾回收, 存在弱 引用的对象 占有略微长的生命周期,当垃圾回收器执行回收操作时,有可 能被垃圾回收, 存在软 引用的对象 占有较长的生命周期,但在Java 虚构机认为内存缺乏的状况下,也会被垃圾回收 。

例程11-15 References.java

import java.lang.ref.*;

import java.util.*;

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);

}

}