SoftReference、WeakReference、PhnatomReference

java中为了实现一些特殊的场景,除了一般的强引用外额外设计了三种引用,软引用、弱引用、虚引用。在jdk中有SoftReference、WeakReference、PhantomReference分别对应软引用、弱引用和虚引用,用这几个类来实现这几种引用。

引用和它们的关系

一般来说这几个引用各自用以下场景解释。

强引用:直接通过new创建出来的对象。只要有强引用存在,jvm gc时就不会(不能)回收。

软引用:通过持有SoftReference对象,SoftReference对象再持有目标对象实现的引用。如下图所示,黄色的箭头可以认为软引用。因为这个引用jvm会特殊对待。如果目标对象只通过这一个软引用可达,jvm只有在将要发生OOM的时候,gc才会会回收这个目标对象。在没有被回收之前通过get方法可以得到该目标对象。被回收后get返回null。
SoftReference、WeakReference、PhnatomReference

弱引用:通过WeakReference实现的引用。与上图类似,如果目标对象只通过这一个弱引用可达,那么jvm在每次gc的时候都会回收这个目标对象。在没有被回收之前get方法可以得到该目标对象的引用。gc后,get方法返回为null。

虚引用:通过PhantomReference实现的引用。与上图类似。不同的是get()方法固定返回null,也就是说无法再获取到这个对象。如果目标对象只有通过这一个虚引用可达,jvm在每次gc的时候都会回收这个对象。

它们的引用强度 强引用 > 软引用 > 弱引用 > 虚引用。如果一个对象有多种类型的引用,按照最强的引用处理规则来处理。例如一个有弱引用的对象同时还有一个强引用,那么这个对象在每次gc的时候就不能回收,当且仅有一个弱引用时,每次gc才会回收。

引用的内部逻辑

SoftReference、WeakReference、PhantomReference除上述的特点外,还有一个重要的设计,就是在gc把对象回收的时候,还可以做一些后置处理的事情。这个逻辑是在父类Reference里实现的。

Reference类中有referent变量,持有目标对象的引用,根据引用类型的不同会被按照相应的规则回收,当目标对象被标记为要回收时,对应Reference的会被gc添加到pending链表上。另一方面在Reference有个守护线程,会轮训pending,从pending链表移除Reference对象并添加到对应的queue队列中,这个queue是在创建Reference对象时(如创建WeakReference时)由用户程序指定的,这样用户程序就可以轮训这个queue,如果queue有对象,说明这个Reference引用对应的目标对象将要被gc,这个时候用户程序可以尝试做些什么。但如果用户没有指定queue,那么守护线程只会从pending链表上移除Reference对象,就没有后继的操作。这个Reference中封装的逻辑是引用管理的最基本的实现机制。
SoftReference、WeakReference、PhnatomReference

WeakReference的使用
@Test
public void testWeakReference(){
    ReferenceQueue<String> queue = new ReferenceQueue<>();
    // String str = "WeakReference"; 不能够这样创建str来验证,这样创建出来的对象是个常量,也就是一直有强引用
    String str = new String("WeakReference");
    WeakReference<String> wr = new WeakReference<>(str, queue);
    System.out.println(wr.get());
    System.gc();
    System.out.println(wr.get());// "WeakReference" 虽然经过gc,但是仍然有str的强引用。不能回收
    str = null;// 移除强引用
    System.gc();
    System.out.println(wr.get());// null ,没有了强引用,经过gc后会回收
}
WeakHashMap的使用

为了更好的理解上述的引用,以java.util.WeakHashMap 例子。WeakHashMap中的Map.Entry继承自WeakReference,对应的key会被赋值到referent上,并且在WeakHashMap中有一个全局的ReferenceQueue赋值到queue上。

Map.Entry中 key 只有弱引用时,经过gc的标记以及Reference类中的逻辑后,全局的queue中会有对应的Map.Entry对象。WeakHashMap中有expunageStaleEntries方法,该方法从queue中取出Map.Entry对象,然后从WeakHashMap的table中移除该Map.Entry对象,既从Map中清除掉了对应的Key、Value。

WeakHashMap的使用

@Test
public void testWeakHashMap(){
    Map<String,String> map = new WeakHashMap<>();
    // String keyStr = "key"; 不能够这样创建keyStr来验证,这样创建出来的对象是个常量,也就是一直有强引用
    String keyStr = new String("key");
    String value = "value";
    map.put(keyStr,value);
    System.out.println(map.get(keyStr));
    System.gc();
    System.out.println(map.get(keyStr));// 输出为value,经过gc后还存在,因为有强引用keyStr
    keyStr = null; // 把强引用移除,再次gc
    System.gc();
    System.out.println(map.get(new String("key")));// 输出为null   
}

ThreadLocal中使用WeakReference和内存泄露。

有时使用ThreadLocal是非常方便的,在ThreadLocal中会存放一些下文可能会使用的数据Data。从实现的源代码上看,用户程序实际上是通过ThreadLocal的实例在Thread中的ThreadLocalMap中存取数据。梳理引用结构如下:
SoftReference、WeakReference、PhnatomReference
每个线程持有ThreadLocalMap,这个map的key是当前ThreadLocal的实例,value是要存储的数据。假如通过某个ThreadLocal实例放置Data数据实例,ThreadLocalMap.Entry key指向ThreadLocal的实例是弱引用的形式。当用户程序清除了ThreadLocal的强引用,待下次gc时,ThreadLocal的实例会被回收,但是ThreadLocal实例被回收时,并没有通知对应的ThreadLocalMap.Entry回收。通过分析源代码发现,对ThreadLocalMap.Entry的回收时被动的,只有在用户程序在调用ThreadLocal实例的set、get、remove方法的时候,才会触发清理key已经是null的Entry。因此对应的Data仍然有通过Thread实例的强引用存在,不能回收。

只要Thread对象没回收,也就是只要线程还在使用,对应的Data就不会回收,但是由于此时用户程序已经回收了ThreadLocal实例,已经没有办法获取到Data了,Data这时对用户程序来说是无法使用的或者用户程序认为这个Data已经回收了。因此这个Data某种意义上讲是“内存泄露“。

如果用户程序还使用了其他的ThreadLocal实例,通过其他ThreadLocal继续放置临时变量时,才能触发对这个Data的清理。如果用户程序不再使用ThreadLocal了,那么这个Data的泄露会伴随着这个线程的整个一生的。

SoftReference、WeakReference、PhnatomReferenceSoftReference、WeakReference、PhnatomReference 玩转生活 发布了1 篇原创文章 · 获赞 1 · 访问量 28 私信 关注
上一篇:Cypress web自动化6- Assertions断言使用(should, expect)


下一篇:Java对象引用