强引用、软引用、弱引用、虚引用

702 阅读5分钟

引言:更多相关请看 JVM+GC解析系列

整体架构

Reference类及其子类位于java.lang.ref包下,SoftReference软引用、WeakReference弱引用、PhantomReference虚引用、ReferenceQueue引用队列。

强引用(默认支持模式)

当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM不会对该对象行回收,死都不收。
强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象,在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄露的主要原因之一。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用默值为null。一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。

案例

代码:

public class ReferenceDemo {
    public static void main(String[] args) throws Exception {
        Object o1 = new Object();// 默认强引用
        Object o2 = o1;// 引用赋值
        o1 = null; // 置空
        System.gc();
        System.out.println(o2);
    }
}

效果:

java.lang.Object@3af49f1c

软引用

是一种相对强引用弱化了一些的引用,需要用ava.lang.ref.SoftwareReference类来实现,可以让对象豁免一些垃圾收集。有软引用的对象来说,当系统内存充足时它不会被回收,当系统内存不足时它会被回收。 通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!

案例

代码:

public class ReferenceDemo {
    public static void main(String[] args) throws Exception {
        /*内存够用的时候就保留,不够用就回收!*/
        // softReference.get()获取引用对象
//        memoryEnough();
        /*
        执行效果:正常执行,未被回收
        java.lang.Object@3af49f1c
        java.lang.Object@3af49f1c
        null
        java.lang.Object@3af49f1c
         */
        memoryNotEnough();
        /*
        执行效果:报异常OOM:Java heap space,对象被回收
        java.lang.Object@3af49f1c
        java.lang.Object@3af49f1c
        java.lang.OutOfMemoryError: Java heap space
            at com.example.demo.test01.ReferenceDemo.memoryNotEnough(ReferenceDemo.java:48)
            at com.example.demo.test01.ReferenceDemo.main(ReferenceDemo.java:16)
        null
        null
        */
    }

    /**
     * 内存够用
     */
    public static void memoryEnough(){
        Object o1 = new Object();// 默认强引用
        SoftReference<Object> softReference = new SoftReference<>(o1);
        System.out.println(o1);
        System.out.println(softReference.get());

        o1 = null; // 置空
        System.gc();// 垃圾回收

        System.out.println(o1);
        System.out.println(softReference.get());
    }

    /**
     * 内存不够用
     * 故意JVM配置小内存产生大对象,让其内存不够用产生OOM
     * JVM配置:-Xms10m --Xmx10m
     */
    public static void memoryNotEnough(){
        Object o1 = new Object();
        SoftReference<Object> softReference = new SoftReference<>(o1);
        System.out.println(o1);
        System.out.println(softReference.get());

        o1 = null;
        try{
            byte[] bytes = new byte[30*1024*1024];// 30m的大对象
        }catch (Throwable e){
            e.printStackTrace();
        }finally {
            System.out.println(o1);
            System.out.println(softReference.get());
        }
    }
}

弱引用

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

案例

代码:

public class ReferenceDemo {
    public static void main(String[] args) throws Exception {
        Object o = new Object();
        WeakReference<Object> weakReference = new WeakReference<>(o);
        System.out.println(o);
        System.out.println(weakReference.get());
        System.out.println("==============================");

        o =null;
        System.gc();// 垃圾回收

        System.out.println(o);
        System.out.println(weakReference.get());

    }
}

执行效果:

java.lang.Object@3af49f1c
java.lang.Object@3af49f1c
==============================
null
null

软引用和弱应用的适用场景

假如有一个应用需要读取大量的本地图片:

如果每次读取图片都从硬盘读取则会严重影响性能;
如果一次性全部加载到内存中又可能造成内存溢出。

此时使用软引用可以解决这个问题。设计思路是:用一个Hashmap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。

HashMap<String, SoftReference<BiMap>> imageCache = new HashMap<>();

WeakHashMap

WeakHashMap源码中有这样一段话

When a key has been discarded its entry is effectively removed from the map...

当一个key无效时,对应的kv键值对也会从map中移除(也就是JVM对其进行垃圾回收)。把内存空间腾出来,相对HashMap而言大量减少OOM发生的概率。
WeakHashmap业务场景就是缓存,可以有效的节省内存,缓存丢失也不会出太大问题,可以再次获取。许多开源框架,例如tomcat等都使用了weakHashmap做为缓存处理。 代码:

public class ReferenceDemo {
    public static void main(String[] args) throws Exception {
        myHashMap();
        System.out.println("=================================");
        myWeakHashMap();
    }

    public static void myHashMap(){
        Map<Integer, String> map = new HashMap<>();
        Integer k = new Integer(1);
        String v = Thread.currentThread().getStackTrace()[1].getMethodName();// 获取当前方法名
        map.put(k, v);
        System.out.println(map + "\t" + map.size());
        k = null;
        System.gc();// 垃圾回收
        System.out.println(map + "\t" + map.size());
    }

    public static void myWeakHashMap(){
        WeakHashMap<Integer, String> map = new WeakHashMap<>();
        Integer k = new Integer(2);
        String v = Thread.currentThread().getStackTrace()[1].getMethodName();// 获取当前方法名
        map.put(k, v);
        System.out.println(map + "\t" + map.size());
        k = null;
        System.gc();// 垃圾回收
        System.out.println(map + "\t" + map.size());
    }
}

结果:

{1=myHashMap}	1
{1=myHashMap}	1
=================================
{2=myWeakHashMap}	1
{}	0

虚引用

虚引用需要java.lang.ref.PhantomReference类米实现。顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被 finalize以后,做某些事情的机制。 PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入 finalization阶段,可以被gc回收,用来实现比 finalization机制更灵活的回收操作。
换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。Java技术允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
引用队列的作用就是保存只有虚引用且被回收了的对象

案例

代码:

public class ReferenceDemo {
    public static void main(String[] args) throws Exception {
        Object o = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(o, referenceQueue);
        System.out.println(o);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("========================");
        o = null;
        System.gc();// 垃圾回收
//        Thread.sleep(300);

        System.out.println(o);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());// 只有产生GC才会把对象保存
    }

}

效果:

java.lang.Object@3af49f1c
null
null
========================
null
null
java.lang.ref.PhantomReference@19469ea2

GCRoots和四大引用的小总结