Java 引用类型-Java快速入门教程
在 JDK 1.2 以前,Java 中的引用定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。在这种定义下,一个对象只有引用和没有被引用两个状态。
在 JDK 1.2 之后,Java 将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐变弱。在这种定义下,一个对象的引用状态被大大丰富,虚拟机也根据对象引用状态确定是否对对象进行回收。
强引用
类似 “Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾收集器永远不会掉被引用的对象。
软引用
描述一些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把可回收的软引用对象进行二次回收,如果回收后还没有足够的内存,才会抛出内存溢出异常。
Java 中提供了 SoftReference 类来实现软引用。
软引用还可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收(即对象的强引用被回收,该对象变成了软可及对象),Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。在软引用还没被垃圾回收之前,通过软引用的 get() 方法可以重新获得强引用,相反,如果软引用也被垃圾回收了,该软引用的 get() 方法就会返回 null。
tips:我们可以通过检查 ReferenceQueue 元素的 get() 方法是否返回 null,来判断该软引用是否已经被回收。
弱引用
也是用来描述非必需对象的,可回收的弱引用对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,可回收的弱引用对象都会被回收。
Java 中提供了 WeakReference 类来实现弱引用。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。同样,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。在弱引用还没被垃圾回收之前,通过弱引用的 get() 方法可以重新获得强引用,相反,如果弱引用也被垃圾回收了,该软引用的 get() 方法就会返回 null。
虚引用
最弱的一种引用关系,一个对象是否有虚引用,完全不会对其生存时间构成影响,也无法通过一个虚引用来取得一个对象实例,为一个对象设置虚引用关联的唯一目的就是能够在这个对象被收集器回收时收到一个系统通知。
Java 提供了 PhantomReference 类来实现虚引用。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中,程序员以此来追踪一个对象的回收情况。
实践
1. 通过软可及对象重获方法实现Java对象的高速缓存
这里的高速缓存是指:当 Java 对象的强引用已经被垃圾回收或被设置成 null 值,但是该对象的软引用还没被回收,这时可以通过软引用的 get() 方法重新获取强引用,避免重新实例化对象。
1.1 Employee
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" this.name = "JMCui"; this.Phone = "15980292662"; this.salary = 5000; this.origin = "From DataBase"; } }
1.2 EmployeeCache
public class EmployeeCache {
// 一个 Cache 实例 private static volatile EmployeeCache cache;
// 用于 Cache 内容的存储 private Hashtable<String, EmployeeRef> employeeRefs;
// 垃圾 Reference 的队列(当软引用对象被回收的时候,会将 SoftReference 保存到该队列) private ReferenceQueue<Employee> queue; // 构建一个缓存器实例 private EmployeeCache() { employeeRefs = new Hashtable<>(); queue = new ReferenceQueue<>(); } // 取得缓存器实例 public static EmployeeCache getInstance() { if (cache == null) { synchronized (EmployeeCache.class) { if (cache == null) { cache = new EmployeeCache(); } } } return cache; } // 以软引用的方式对一个Employee对象的实例进行引用并保存该引用 private void cacheEmployee(Employee em) { // 清除垃圾引用 cleanCache(); EmployeeRef ref = new EmployeeRef(em, queue); employeeRefs.put(em.getId(), ref); } // 依据所指定的ID号,重新获取相应Employee对象的实例 public Employee getEmployee(String id) { Employee em = null; // 缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。 if (employeeRefs.containsKey(id)) { EmployeeRef ref = employeeRefs.get(id); em = ref.get(); } // 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例, // 并保存对这个新建实例的软引用 if (em == null) { em = new Employee(id); System.out.println("Retrieve From EmployeeInfoCenter. ID=" + id); cacheEmployee(em); } return em; } // 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象 private void cleanCache() { EmployeeRef ref; while ((ref = (EmployeeRef) queue.poll()) != null) { employeeRefs.remove(ref._key); } } // 继承SoftReference,使得每一个实例都具有可识别的标识。 // 并且该标识与其在 HashMap 内的key相同。 // 如果 SoftReference 对象没有被回收,则通过 SoftReference.get() 可以返回强引用对象,否则返回 null。 private class EmployeeRef extends SoftReference<Employee> { private String _key; public EmployeeRef(Employee em, ReferenceQueue<Employee> q) { super(em, q); _key = em.getId(); } } public static void main(String[] args) { EmployeeCache employeeCache = getInstance(); Employee employee1 = employeeCache.getEmployee("11111"); System.out.println("employee1:" + employee1.getName()); employee1 = null; Employee employee2 = employeeCache.getEmployee("11111"); System.out.println("employee2:" + employee2.getName()); employee2 = null; System.gc(); Employee employee3 = employeeCache.getEmployee("11111"); System.out.println("employee3:" + employee3.getName()); } }
2. WeakHashMap 实践
WeakHashMap 的 key 是一个弱引用的对象,当一个键对象被垃圾回收器回收时,那么相应的值对象的引用会从Map中删除。
public class WeakMapManager { public static class Element { private String ident; public Element(String id) { ident = id; } @Override public String toString() { return ident; } @Override public int hashCode() { return ident.hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof Element && ident.equals(((Element) obj).ident); } } public static class Key extends Element { public Key(String id) { super(id); } } public static class Value extends Element { public Value(String id) { super(id); } }
public static void main(String[] args) { int size = 20; // 该数组的作用,仅仅只是维护一个强引用 Key[] keys = new Key[size]; Map<Key, Value> map = 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; map.put(k, v); } System.gc(); System.out.println(map); } }
从打印结果可以看出,当执行 System.gc() 方法后,垃圾回收器只会回收那些仅仅持有弱引用的 Key 对象,id 可以被 3 整除的 Key 对象持有强引用,因此不会被回收。