强引用、软引用、弱引用、虚引用、引用队列、WeakHashMap

从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
Java中提供这四种引用类型主要有两个目的:

  • 第一是可以让程序员通过代码的方式决定某些对象的生命周期;
  • 第二是有利于JVM进行垃圾回收。
    image

1.强引用(默认支持)

强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会回收这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量, 这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收(宁可抛出OOM异常)。因此强引用是造成Java内存泄漏的主要原因之一。

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null, 一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。

public class ReferenceDemo {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = obj1;
        obj1 = null;
        System.gc(); // 提醒虚拟机,希望进行一次垃圾回收
        System.out.println(obj2);
    }
}

image

System.gc()
直到看完System.gc()的源码之后才搞清楚,执行System.gc()函数的作用只是提醒或告诉虚拟机,希望进行-次垃圾回收。
至于什么时候进行回收还是取决于虚拟机,且也不能保证-定进行回收(如果-XX:+DisableExplicitGC设置成true, 则不会进行回收)。

2.软引用

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReferenge类来实现,可以让对象豁免-些垃圾收集。
对于只有软引用的对象来说:

  • 当系统内存充足时它不会被回收
  • 当系统内存不足时它才会被回收

使用场景
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!
一个应用需要读取大量的本地图片,如果每次读取都从硬盘读取会严重影响性能,如果一次性全部加载到内存,内存可能会溢出。
设计思路是:
用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
**Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>**

/**
 * @author smileha
 * @create 2022-03-06 11:07
 * @description 内存够用不回收,内存不够用要进行回收
     vm option -Xms5m -Xmx5m
 */
public class SoftReferenceDemo {
    public static void main(String[] args) {
        SoftReferenceDemo softReferenceDemo = new SoftReferenceDemo();
//        softReferenceDemo.SoftRef_Memory_Enough(); // 内存足够
        softReferenceDemo.SoftRef_Memory_NotEnough(); // 内存不够
    }

    // 内存足够
    public void SoftRef_Memory_Enough(){
        Object obj1 = new Object();
        // 将软引用指向obj对象
        SoftReference softReference = new SoftReference<>(obj1);
        obj1 = null;
        System.out.println(softReference.get()); // 获取将软引用指向的对象
    }

    // 内存不够
    public void SoftRef_Memory_NotEnough(){
        Object obj1 = new Object();
        // 将软引用指向obj对象
        SoftReference softReference = new SoftReference<>(obj1);
        obj1 = null;
        System.out.println("********before:" + softReference.get()); // 获取将软引用指向的对象
        try {
            byte[] bytes = new byte[10 * 1024 * 1024]; // 造了大对象
        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println("********88after:" + softReference.get());
        }
    }
}

image
image

3.弱引用

弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。
对于只有弱引用的对象来说,只要垃圾回收机制一-运行(若没有进行gc也不会回收),不管JVM的内存空间是否足够,都会回收该对象占用的内存。

/**
 * @author smileha
 * @create 2022-03-06 11:25
 * @description 弱引用:不管内存是否够用,只要有gc都进行回收
 */
public class WeakReferenceDemo {
    public static void main(String[] args) {
        Object obj1 = new Object();
        WeakReference<Object> weakReference = new WeakReference<>(obj1);
        obj1 = null;
        System.gc(); // 建议进行垃圾回收
        System.out.println("weakReference: " + weakReference.get());
    }
}

image
image

4.虚引用

虚引用(幽灵引用)需要java.lang.ref.PhantoMReference类来实现。
顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。

应用:
虛引用的主要作用是跟踪对象被垃圾回收的状态。
设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的 处理。

public class PhantomReferenceDemo {
    public static void main(String[] args) {
        Object obj1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference phantomReference = new PhantomReference(obj1,referenceQueue);
        System.out.println("==========gc之前==========");
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());
        obj1 = null;
        System.gc();
        System.out.println("==========gc之后==========");
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

    }
}

image

5.引用队列

引用队列,注册的引用型对象(SoftReference、WeakReference、PhantomReference)在垃圾回收器检测到所引用的对象可达性发生改变时,会将这个引用型的对象添加到引用队列中
当创建一个非强引用的引用对象时,可以传一个引用队列对象给Reference构造函数。引用队列是GC通知程序某个对象不可达的信号,装载这个不可达对象引用的容器。

方法:
poll():移出并返回队列中的头节点,非阻塞,立即返回
remove():移出并返回队列中的头节点,头节点为null,阻塞,直到队列中添加元素,可以设置超时时间
以弱引用+引用队列为例:

public class ReferenceQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        ReferenceQueue referenceQueue = new ReferenceQueue();
        WeakReference<Object> weakReference = new WeakReference<>(obj,referenceQueue);
        obj = null;
        System.out.println("=============gc前============");
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());
        System.out.println("==============gc后===========");
        System.gc();
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.remove());
    }
}

image

分析:
垃圾回收器会清理掉被弱引用引用着的对象,或者说是具有弱可达性的对象来作为垃圾回收的过程中的一部分。该过程可以这样描述: 如果垃圾回收器发现一个对象具有弱可达性,那么它会

1.将弱引用(WeakReference)对象的实际引用字段设置为null,因此可以使它不再引用着堆中的对象。

2.被弱引用引用着的堆中的对象会被声明为是可终结的(finalizable)。

3.弱引用对象被添加到它的引用队列中。然后在堆中的那个对象会调用finalize()方法,它的内存会被释放掉。

注意:
引用队列实际上只是持有着已经不再引用堆中的要被清除的对象的引用型对象。因此这个弱引用和内存中的对象没有任何关联。
通俗点说就是:如果一个引用被加入到了引用队列中,那么我们就无法再通过这个引用去找到他引用的对象,调用get()只会返回null。

但是,finalize方法可以被用来重新依附到对象上,使他们具有强可达性。
Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
关于finalize():不建议使用https://baijiahao.baidu.com/s?id=1655232869611610920&wfr=spider&for=pc

6.WeakHashMap

1.WeakHashMap 继承于AbstractMap,实现了Map接口。
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {}
2.和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,键和值都可以是null,如果key是null,将返回一个常量new Object()。

private static final Object NULL_KEY = new Object();
 // Use NULL_KEY for key if it is null.
private static Object maskNull(Object key) {
    return (key == null) ? NULL_KEY : key;
}

3.和HashMap一样,WeakHashMap是不同步的。可以使用
Collections.synchronizedMap 方法来构造同步的 WeakHashMap。

4.它也是用Entry节点对象封装key-value。但是不同的是,它的静态内部类Entry又继承了WeakReference,也就是说,它的Entry对象本身就是一个弱引用对象

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value; // 封装值
    final int hash; // key的hash值
    Entry<K,V> next; // 下一个节点对象
​
    Entry(Object key, V value,
          ReferenceQueue<Object> queue,
          int hash, Entry<K,V> next) {
      /**
     super(key, queue);实际上调用了父类的构造方法:
     public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
      将key作为该弱引用所引用的对象,并传入了引用队列
      **/
        super(key, queue);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }
    .......
}

那么基于这种结构,WeakHashMap中的key在没有其他引用的情况下,只要发生了GC,便会被回收掉。

5.如果一个key被回收了,那么它对应的Entry也会从map中被移出
怎么做的呢?

就是通过同步table和queue,我们知道在弱引用所引用的对象被回收前,会将弱引用对象本身放入引用队列,在WeakHashMap中这个弱引用对象就是Entry。

在对WeakHashMap进行其他操作之前(put/get/remove...)之前都会将queue中的entry从table中移除掉。

public V put(K key, V value) {
    Object k = maskNull(key);
    int h = hash(k);
    Entry<K,V>[] tab = getTable(); // 获取Entry[]前,先同步queue和table,返回移除后的table
    .....
}

private Entry<K,V>[] getTable() {
    // 移出entry,此方法不展开讲,实际上他做的事情非常好理解,就是遍历queue,将poll()出的entry从table中移出
    expungeStaleEntries(); 
    return table;
}

使用场景:

WeakHashMap的这种特性比较适合实现类似本地、堆内缓存的存储机制——缓存的失效依赖于GC收集器的行为。

/**
 * @author smileha
 * @create 2022-03-06 11:47
 * @description
 */
public class WeakHashMapDemo {
    public static void main(String[] args) {
        System.out.println("==========HashMap============");
        myHashMap();
        System.out.println("==========WeakHashMap============");
        weakHashMap();
​
    }
​
    private static void myHashMap() {
        HashMap<Integer, String> hashMap = new HashMap<>();
        Integer k = new Integer(1);
        String v = "HashMap";
        hashMap.put(k,v);
        System.out.println(hashMap);
        k = null;
        System.out.println("k = null,hashMap:"+hashMap);
        System.gc();
        System.out.println("gc后,hashMap:"+hashMap);
​
    }
    private static void weakHashMap() {
        WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<>();
        Integer k = new Integer(2);
        String v = "WeakHashMap";
        weakHashMap.put(k,v);
        System.out.println(weakHashMap);
        k = null;
        System.out.println("k = null,weakHashMap:"+weakHashMap);
        System.gc();
        System.out.println("gc后,weakHashMap:"+weakHashMap);
    }
}

image

参考:

https://blog.csdn.net/m0_60907200/article/details/123776701

posted @ 2023-04-05 17:13  Jimmyhus  阅读(15)  评论(0编辑  收藏  举报