也就是说,仅仅有对象处于可触及(reachable)状态。程序才干使用它。从JDK 1.2版本号開始,把对象的引用分为4种级别。从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。下图为对象应用类层次。
仅仅要垃圾回收器没有回收它。该对象就能够被程序使用。软引用可用来实现内存敏感的快速缓存(下文给出演示样例)。
只是,因为垃圾回收器是一个优先级非常低的线程,因此不一定会非常快发现那些仅仅具有弱引用的对象。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
|
假设程序发现某个虚引用已经被增加到引用队列,那么就能够在所引用的对象的内存被回收之前採取必要的行动。
由此带来了一个问题,那就是某个对象的可及性怎样推断:
作为一个用户,我们全然有可能须要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(相同。我们在浏览WEB页面的时候也常常会使用“后退”button)。这时我们一般会有两种程序实现方式:一种是把过去查看过的雇员信息保存在内存中,每个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;还有一种是当用户開始查看其它雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用。使得垃圾收集线程能够回收其所占用的内存空间,当用户再次须要浏览该雇员的档案信息的时候,又一次构建该雇员的信息。非常显然。第一种实现方法将造成大量的内存浪费,而另外一种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包括雇员档案信息的对象仍然完善地保存在内存中。应用程序也要又一次构建一个对象。我们知道,訪问磁盘文件、訪问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,假设能又一次获取那些尚未被回收的Java对象的引用,必将降低不必要的訪问,大大提高程序的执行速度。
也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。
另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。
MyObject aRef = new MyObject();
SoftReference aSoftRef=new SoftReference(aRef);
|
aRef = null;
|
Java虚拟机的垃圾收集线程对软可及对象和其它一般Java对象进行了差别对待:软可及对象的清理是由垃圾收集线程依据其特定算法依照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,并且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。在回收这些对象之前,我们能够通过:
MyObject anotherRef=(MyObject)aSoftRef.get();
|
假设在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为參数提供给SoftReference的构造方法,如:
ReferenceQueue queue = new ReferenceQueue();
SoftReference ref=new SoftReference(aMyObject, queue);
|
也就是说,ReferenceQueue中保存的对象是Reference对象,并且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也能够看出,它是一个队列,当我们调用它的poll()方法的时候。假设这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
假设队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这种方法。我们能够检查哪个SoftReference所软引用的对象已经被回收。于是我们能够把这些失去所软引用的对象的SoftReference对象清除掉。经常使用的方式为:
SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
// 清除ref
}
|
public class Employee {
private String id;// 雇员的标识号码
private String name;// 雇员姓名
private String department;// 该雇员所在部门
private String Phone;// 该雇员联系电话
private int salary;// 该雇员薪资
private String origin;// 该雇员信息的来源
// 构造方法
public Employee(String id) {
this.id = id;
getDataFromlnfoCenter();
}
// 到数据库中取得雇员信息
private void getDataFromlnfoCenter() {
// 和数据库建立连接井查询该雇员的信息,将查询结果赋值
// 给name,department。plone,salary等变量
// 同一时候将origin赋值为"From DataBase"
}
……
|
以下是一个对Employee对象进行缓存的缓存器的定义:
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;
public class EmployeeCache {
static private EmployeeCache cache;// 一个Cache实例
private Hashtable<String,EmployeeRef> employeeRefs;// 用于Chche内容的存储
private ReferenceQueue<Employee> q;// 垃圾Reference的队列
// 继承SoftReference,使得每个实例都具有可识别的标识。
// 而且该标识与其在HashMap内的key同样。
private class EmployeeRef extends SoftReference<Employee> {
private String _key = "";
public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
super(em, q);
_key = em.getID();
}
}
// 构建一个缓存器实例
private EmployeeCache() {
employeeRefs = new Hashtable<String,EmployeeRef>();
q = new ReferenceQueue<Employee>();
}
// 取得缓存器实例
public static EmployeeCache getInstance() {
if (cache == null) {
cache = new EmployeeCache();
}
return cache;
}
// 以软引用的方式对一个Employee对象的实例进行引用并保存该引用
private 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);
}
}
|