先说结论:
首先,Java中有四种引用类型:强引用、软引用、弱引用、虚引用。-- 在 Java 1.2 中添加的,见 package java.lang.ref; 。
其次,这几个概念是与垃圾回收有关的。
然后,如果你不知道这几个概念,那你用的肯定都是强引用。例如 String str = new String(); 这个 str 到 new String() 的引用类型就是强引用。
那么弱引用是什么?
弱引用,就是引用与对象之间的联系很弱,弱到垃圾回收器会无视这个引用,直接回收对象。
软引用与弱引用类似,但只在内存不足时才会被回收。
虚引用最差,甚至不能通过 get() 获取到对象。
为什么需要弱引用?
先看一段代码:
1 package cn.larry.weakref.pojo; 2 3 public final class Product { 4 private String name; 5 6 public Product() { 7 super(); 8 } 9 10 public Product(String name) { 11 super(); 12 this.name = name; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 @Override 24 public String toString() { 25 return "Product [name=" + name + "]"; 26 } 27 28 }
1 package cn.larry.weakref.test; 2 3 import java.lang.ref.ReferenceQueue; 4 import java.lang.ref.WeakReference; 5 import java.util.HashMap; 6 import java.util.Map; 7 import java.util.WeakHashMap; 8 9 import org.junit.Test; 10 11 import cn.larry.weakref.pojo.Product; 12 13 public class WrTest { 14 private Product product = new Product("iPhone"); 15 16 @Test 17 public void strongRef() { 18 Map<Product, Integer> map = new HashMap<>(); 19 map.put(product, 111); 20 21 product = null;// 关键 22 map.keySet().forEach(System.out::println);// 对象仍然存在! 23 } 24 }
在上面的代码中,虽然在第21行已经将 product引用置空(null),但是第22行的代码仍然可以访问到该引用最初指向的对象!
这时,如果map中有多个Key,Product类又不可修改(不能添加识别字段),那你怎么确定并删除这个Key,能否自动移除该Key?弱引用就可以满足这样的要求。
一个经典的场景就是缓存,特别是缓存图片(加载到内存中),此时缓存肯定会包含一个指向内存中图片缓存的引用。问题在于,如果使用强引用,你必须决定何时不再需要该缓存并手动移除它。
怎么使用弱引用?
代码如下(与上面的测试代码在同一个 WrTest 类中):
1 @Test 2 public void weakRef_1() { 3 4 ReferenceQueue<Product> rq = new ReferenceQueue<>(); 5 // WeakReference<Product> wr=new WeakReference<Product>(product); 6 WeakReference<Product> wr = new WeakReference<Product>(product, rq);// 引用队列 7 System.out.println("wr是否已被添加至引用队列:" + wr.isEnqueued()); 8 9 Map<WeakReference<Product>, Integer> map = new HashMap<>(); 10 map.put(wr, 111); 11 12 product = null;// 关键 13 // System.gc();// 关键 14 15 map.keySet().forEach(e -> System.out.println(e.get()));// 不gc()的话,还能访问对象,否则null。 16 System.out.println("wr是否已被添加至引用队列:" + wr.isEnqueued()); 17 }
上面第4行构造了一个引用队列。
第5或6行构造了一个弱引用,区别在于是否指定引用队列。
如果指定了弱引用的引用队列,垃圾回收器会在回收对象之后将该弱引用添加至引用队列中 -- 可以在回收之后通过弱引用的 isEnqueued() 方法来判断。
需要注意:①必须将原强引用置空(第12行),且不能有其他强引用指向该对象;②必须主动调用垃圾回收器(第13行)。然后就能看到弱引用指向的对象已经不存在了(第15行) -- 弱引用本身还存在,所以map不为空。
但是,这时已经可以手动删除这个Key了。
更便捷的方法:
JDK提供了 WeakHashMap 可以更加方便的完成上面的工作,代码如下:
1 @Test 2 public void weakRef_2() { 3 WeakHashMap<Product, Integer> map = new WeakHashMap<>(); 4 map.put(product, 111); 5 6 product = null;// 关键 7 // System.gc();//关键 8 9 map.keySet().forEach(System.out::println);// 不gc()的话,还能访问对象,否则null。 10 11 }
这种情况下,map.size()是0,即该Key自动被移除!!!
补充说明:
1、虽然可以主动调用垃圾回收器,但垃圾回收器并不总是会执行的。
2、Product类参考了 理解Java中的弱引用(Weak Reference) 中的pojo类。
参考
理解Java中的弱引用(Weak Reference)译文:理解Java中的弱引用