今天学习了关于java中引用的一个知识点。在博客里记录一下。


1、概念解释

强引用,就是一般引用,如String a = new String("abc"); String b = a + "bc";在放弃引用钱垃圾回收绝对不回收的。

软引用,垃圾回收器发现没有内存空间时就会回收弱引用。

弱引用,垃圾回收器每次运行只要发现是弱引用就一定回收。

虚引用,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。


2、使用目的

为什么使用软引用,目的是能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,提高运行效率。


3、关于引用队列

为什么使用引用队列ReferenceQueue?因为SoftReference引用的对象被GC掉之后,SoftReference对象本身也没有用了,所以将SoftReference对象put到一个队列中(这个过程是系统自动完成),这个队列就是ReferenceQueue,可以自己写程序定时清除无效的SoftReference对象。

4、应用场景

①比如,可以构建一个高速对象缓存(通过软可及对象重获方法实现Java对象的高速缓存)

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类的构造方法中我们可以预见,如果每次需要查询一个雇员的信息。哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这是需要消耗很多时间的。下面是一个对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);
       }
    }
 
    // 清除Cache内的全部内容
    public void clearCache() {
       cleanCache();
       employeeRefs.clear();
       System.gc();
       System.runFinalization();
    }
}



②在ThreadLocal中使用软引用

alibaba-fastjson类JSONReaderScan中有这样一行

private final static ThreadLocal<SoftReference<char[]>> BUF_REF_LOCAL = new ThreadLocal<SoftReference<char[]>>();

首先使用ThreadLocal目的是保证每个线程有自己独立的一份BUF_REF_LOCAL对象,保存char数组,其次使用SoftReference对象是为了缓存char对象,提升程序性能。