# 引用类型

# 强引用

最普遍的引用,如果一个对象具有强引用,GC 绝不会回收。如果不使用,要弱化引用

Object o = new Object();
o = null;// help GC
public void test() {
    Object o = new Object();
}
/**
test 方法内部有一个强引用,引用保存在栈中,new 的对象保存在
堆中,当方法运行结束会退出方法栈,则引用内容的引用不存在,Object
就会被回收。
*/

如果 o 是全局变量,需要在不用对象时赋值为 null。

# 软引用

一个对象只有软引用,在内存空间足够时,垃圾回收器不会回收它;当内存空间不足,就会回收这些对象的内存。

软引用可以用于实现内存敏感的高速缓存。

String str = new String("abc");
// 弱引用
SoftReference<String> softReference = new SoftReference<>(str);

浏览器的后退按钮,后退时显示的网页应该是重新进行请求还是从缓存中取出的取决于具体的实现策略。如果将浏览的网页全部强引用存储到内存中,就会造成浪费甚至内存溢出。

此时使用软引用:

Browser prev = new Browser();// 页面浏览
// 浏览后设为软引用
SoftReference sr = new SoftReference(prev);
if(sr.get != null) {
    rev = (Browser) sr.get();
} else {
    prev = new Broswer();// 内存吃紧,软引用对象回收
    sr = new SoftReference(prev);// 重新构建
}

# 弱引用

区别于软引用,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str=null;
// 可以恢复强引用
String abc = abcWeakRef.get();

# 虚引用

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

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

# WeakHashMap

先看一下引用队列

  • 一般情况,一个对象被标记为垃圾(不代表回收了),会加入到引用队列。
  • 对于虚引用来说,它指向的对象会只有被回收后才会加入到引用队列(其他是被标记),所以可以用作记录该引用指向的对象是否被回收。

WeakHashMapHashMap 没什么区别,只是引用采取的是弱引用的 key 存储,GC 回收的是 key 。这种结构适合缓存处理。底层是因为 Entry 继承了 WeakReference

private static class Entry<K,V> extends 
    							WeakReference<Object>
                                implements
                                Map.Entry<K,V> 
{
    V value;
    final int hash;
    Entry<K,V> next;
    Entry(Object key, V value,
          ReferenceQueue<Object> queue,
          int hash, Entry<K,V> next) {
        super(key, queue);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }
}

为了将 key 改写为弱引用,在 Entry 中又调用了一个 super(key, queue)

// WeakReference 构造方法
public WeakReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
}

WeakHashMap 除了 WeakReference ,还有 ReferenceQueue 重要。

再次强调一下, WeakHashMap 基于弱引用可以淘汰内部元素,同样的, LinkedHashMap 也是具备淘汰机制的,只不过是通过阈值来限定节点个数的先进先出缓存。

# 弱引用回收

Reference 类有一段静态代码:

static private class Lock { };
// 有一个全局锁
private static Lock lock = new Lock();  
private static Reference pending = null;
          
static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent());
    Thread handler = new ReferenceHandler(tg, "Reference Handler");
   
    // 优先级最高
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();
}

假设 WeakHashMap 对象里面保存了很多对象引用:

  • GC 时,会创建一个 CMST 线程进行 GC,该线程被创建的同时还会创建一个 SLT 线程并启动。
  • CMST 开始 GC 时,会发送一个消息给 SLT 让他去获取 Java 层 Reference 对象的全局锁 lock
  • 当 GC 完毕时,JVM 会将 WeakHashMap 中所有被回收的对象所属的 WeakReference 容器对象放入到 Referencepending 属性中(每次 GC 完毕后, pending 属性基本上不会为 null 了)。
  • 然后通知 SLT 释放并且 notify 全局锁 lock ,此时激活了 ReferenceHandler 线程的 run 方法,脱离 wait 状态开始运行。
  • Handler 线程会将 pending 中所有 WeakReference 对象移动到各自队列中(比如当前这个 WeakReference 属于某个 WeakHashMap 对象,那么它就会被放入相应的 ReferenceQueue 列队里面(该列队是链表结构))。

最后看一下 ReferenceHandler 的源码,如何将 WeakRenference 压入队列:

private static class ReferenceHandler extends Thread {
     ReferenceHandler(ThreadGroup g, String name) {
         super(g, name);
     }
     public void run() {
         for (;;) {
         Reference r;
         synchronized (lock) {
             if (pending != null) {
             r = pending;
             Reference rn = r.next;
             pending = (rn == r) ? null : rn;
             r.next = r;
             } else {
             try {
                 lock.wait();
             } catch (InterruptedException x) { }
             continue;
             }
         }
         // Fast path for cleaners
         if (r instanceof Cleaner) {
             ((Cleaner)r).clean();
             continue;
         }
         ReferenceQueue q = r.queue;
         if (q != ReferenceQueue.NULL) q.enqueue(r);
         }
     }
 }

在调用 get()replaceAll()put()remove() 等方法时(都需要获取 table ), 不是直接拿到 table 数组,而是通过 getTable() 方法先把数组中 keynullEntry 删除掉在返回。

private Entry<K,V>[] getTable() {
    // 真正删除 Entry 的执行者 
    expungeStaleEntries();
    return table;
}

expungeStaleEntries() 方法中,存储被 GC 回收对象的 queue 会接收 GC 发送的回收消息,将 queue(通过 synchronized 上锁)中的 key 对应的 value 赋值为 null ,即 help GC

Entry 中保存了 hash 码,即使 key 被 GC 了,仍可以通过 hash 码来定位需要被删除的 Entry 。通过 Entry 的地址在冲突链表中定位(其实我觉得也可以判断 Entry.key == null

# 参考

https://www.cnblogs.com/phlive/p/6030446.html

https://www.jianshu.com/p/2ba55345fd94

一文带你了解 WeakHashMap.

这篇讲得可以:https://blog.csdn.net/weixin_33774883/article/details/89613271