本篇文章尝试从 What,Why,How 这三个角度来探索 Java 中的弱引用, 帮助大家理解 Java 中弱引用的定义, 基本使用场景和使用方法. 由于个人水平有限, 叙述中难免存在不准确或是不清晰的地方, 希望大家可以指出, 谢谢大家:)
What-- 什么是弱引用?
Java 中的弱引用具体指的是 java.lang.ref.WeakReference 类, 我们首先来看一下官方文档对它做的说明:
弱引用对象的存在不会阻止它所指向的对象被垃圾回收器回收. 弱引用最常见的用途是实现规范映射 (canonicalizing mappings, 比如哈希表). 假设垃圾收集器在某个时间点决定一个对象是弱可达的(weakly reachable)(也就是说当前指向它的全都是弱引用), 这时垃圾收集器会清除所有指向该对象的弱引用, 然后把这个弱可达对象标记为可终结(finalizable) 的, 这样它随后就会被回收. 与此同时或稍后, 垃圾收集器会把那些刚清除的弱引用放入创建弱引用对象时所指定的引用队列 (Reference Queue) 中.
实际上, Java 中存在四种引用, 它们由强到弱依次是: 强引用, 软引用, 弱引用, 虚引用. 下面我们简单介绍下除弱引用外的其他三种引用:
强引用(Strong Reference): 通常我们通过 new 来创建一个新对象时返回的引用就是一个强引用, 若一个对象通过一系列强引用可到达, 它就是强可达的(strongly reachable), 那么它就不被回收
软引用(Soft Reference): 软引用和弱引用的区别在于, 若一个对象是弱引用可达, 无论当前内存是否充足它都会被回收, 而软引用可达的对象在内存不充足时才会被回收, 因此软引用要比弱引用 "强" 一些
虚引用(Phantom Reference): 虚引用是 Java 中最弱的引用, 那么它弱到什么程度呢? 它是如此脆弱以至于我们通过虚引用甚至无法获取到被引用的对象, 虚引用存在的唯一作用就是当它指向的对象被回收后, 虚引用本身会被加入到引用队列中, 用作记录它指向的对象已被回收.
Why-- 为什么使用弱引用?
考虑下面的场景: 现在有一个 Product 类代表一种产品, 这个类被设计为不可扩展的, 而此时我们想要为每个产品增加一个编号. 一种解决方案是使用 HashMap<Product, Integer>. 于是问题来了, 如果我们已经不再需要一个 Product 对象存在于内存中(比如已经卖出了这件产品), 假设指向它的引用为 productA, 我们这时会给 productA 赋值为 null, 然而这时 productA 过去指向的 Product 对象并不会被回收, 因为它显然还被 HashMap 引用着. 所以这种情况下, 我们想要真正的回收一个 Product 对象, 仅仅把它的强引用赋值为 null 是不够的, 还要把相应的条目从 HashMap 中移除. 显然 "从 HashMap 中移除不再需要的条目" 这个工作我们不想自己完成, 我们希望告诉垃圾收集器: 在只有 HashMap 中的 key 在引用着 Product 对象的情况下, 就可以回收相应 Product 对象了. 显然, 根据前面弱引用的定义, 使用弱引用能帮助我们达成这个目的. 我们只需要用一个指向 Product 对象的弱引用对象来作为 HashMap 中的 key 就可以了.
- public class WarkRef {
- private final static Map<String,WeakReference<Produce>> STORE_PRODUCE = new HashMap<String,WeakReference<Produce>>();
- public static void addProduce(){
- String produceId = "Produce_ID";
- Produce p = null;
- WeakReference<Produce> wp = null;
- for(int i = 0;i <1000;i++){
- p = new Produce("name"+i);
- wp = new WeakReference<Produce>(p);
- STORE_PRODUCE.put(produceId+i,wp);
- }
- }
- public static void main(String[] args) {
- addProduce();
- //System.gc(); 如果此处去掉注释, 则会报 NullException
- for(Map.Entry<String,WeakReference<Produce>> e : STORE_PRODUCE.entrySet()){
- Produce p = e.getValue().get();
- System.out.println(p.toString());
- }
- }
- static class Produce{
- //...
- private String name;
- public Produce(String name) {
- this.name = name;
- }
- @Override
- public String toString() {
- return "Produce{" +
- "name='" + name + '\'' +
- '}';
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- }
How-- 如何使用弱引用?
拿上面介绍的场景举例, 我们使用一个指向 Product 对象的弱引用对象来作为 HashMap 的 key, 只需这样定义这个弱引用对象:
- Product productA = new Product(...);
- WeakReference<Product> weakProductA = new WeakReference<>(productA);
现在, 若引用对象 weakProductA 就指向了 Product 对象 productA. 那么我们怎么通过 weakProduct 获取它所指向的 Product 对象 productA 呢? 很简单, 只需要下面这句代码:
Product product = weakProductA.get();
实际上, 对于这种情况, Java 类库为我们提供了 WeakHashMap 类, 使用和这个类, 它的键自然就是弱引用对象, 无需我们再手动包装原始对象. 这样一来, 当 productA 变为 null 时(表明它所引用的 Product 已经无需存在于内存中), 这时指向这个 Product 对象的就是由弱引用对象 weakProductA 了, 那么显然这时候相应的 Product 对象时弱可达的, 所以指向它的弱引用会被清除, 这个 Product 对象随即会被回收, 指向它的弱引用对象会进入引用队列中.
引用队列
下面我们来简单地介绍下引用队列的概念. 实际上, WeakReference 类有两个构造函数:
- // 创建一个指向给定对象的弱引用
- WeakReference(T referent)
- // 创建一个指向给定对象并且登记到给定引用队列的弱引用
- WeakReference(T referent, ReferenceQueue<? super T> q)
我们可以看到第二个构造方法中提供了一个 ReferenceQueue 类型的参数, 通过提供这个参数, 我们便把创建的弱引用对象注册到了一个引用队列上, 这样当它被垃圾回收器清除时, 就会把它送入这个引用队列中, 我们便可以对这些被清除的弱引用对象进行统一管理.
参考资料
WeakReference (Java Platform SE 7 ) - Oracle Help Center
理解 Java 中的弱引用
来源: https://juejin.im/post/5c80e2376fb9a049ed31a4c6