【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列

文章目录

  • 一.Java中的四种引用
  • 二.Java为什么要设计这四种引用
  • 三.了解四种引用类型
    • 1.强引用
    • 2.软引用
    • 3.弱引用
    • 4.引用队列
    • 5.虚引用(对象回收和跟踪)
    • 6.总结

一.Java中的四种引用

Java中有四种引用类型依次为:

  • 强引用(Strong Reference
  • 软引用(Soft Reference
  • 弱引用(Weak Reference
  • 虚引用(Phantom Reference

这四种引用强度依次逐渐减弱

二.Java为什么要设计这四种引用

Java 中引入四种引用的目的是让程序自己决定对象的生命周期,JVM 是通过垃圾回收器GC对这四种引用做不同的处理,来实现对象生命周期的改变。

JDK 8中的 UML关系图
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第1张图片
FinalReference 类是包内可见,其他三种引用类型均为 public,可以在应用程序中直接使用。

Java设计这四种引用的主要目的有两个:

  • 可以让程序员通过代码的方式来决定某个对象的生命周期;
  • 有利用垃圾回收

三.了解四种引用类型

1.强引用

  • 强引用是最普遍的一种引用,我们写的代码,99.9999%都是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。类似 Object obj = new Object()这类的引用。只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。

  • 当一个对象被强引用变量引用时,它处于可达状态,是不可能被垃圾回收器回收的,即使该对象永远不会被用到也不会被回收

    Java垃圾回收器GC采用可达性分析法 来确认对象是否存活。如果一个对象没有任何对象所引用,这个对象属于不可达对象,在下一次GC会被回收。反之,当一个对象处于可达对象,该对象不会被GC所回收

  • 当内存空间不足,Java虚拟机宁愿抛出OOM错误,使程序异常终止,也不会回收这种对象。因此强引用有时也是造成Java 内存泄露的原因之一。

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

例如:

StringBuffer str = new StringBuffer("hello world");

局部变量str会被放到里,而StringBuffer实例对象会被放在堆内,局部变量str指向堆内的StringBuffer对象,通过str可以操作该对象,那么str就是StringBuffer的强引用
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第2张图片
当执行这条语句

StringBuffer str1 = str;


【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第3张图片
此时这两个引用都是强引用

强引用具备如下特点:

  1. 通过强引用可以直接访问目标对象
  2. 强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM(内存溢出)异常,也不会回收强引用所指向的对象
  3. 强引用可能导致内存泄漏(站着空间不释放,积累的多了内存泄漏会导致内存溢出)

代码演示

    @Test
    public void testStrongReference() {
        Object o1 = new Object();
        Object o2 = o1;
        
        //将强引用去除
        o1 = null;
        
        System.gc();
        System.out.println(o1);  //null
        System.out.println(o2);  //java.lang.Object@2503dbd3
    }

执行结果
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第4张图片
o1已经被回收,但是 o2 强引用 o1,一直存在,所以不会被GC回收

那么什么时候才可以被回收呢
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

o1 = null;
o2 = null;

2.软引用

  • 软引用是相对强引用弱化了一些的引用,Java1.2后用java.lang.ref.SoftReference类来实现,可以让对象避免一些垃圾收集。

    软引用就是把对象用SoftReference包裹一下,当我们需要从软引用对象获得包裹的对象,只要get一下就可以了:

  • 主要用来描述一些还有用,但并非必需的对象被软引用变量引用的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。

  • 对于只有软引用的对象来说:当系统内存充足时它不会被回收,当系统内存不足时它才会被回收。

代码演示
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第5张图片

import java.lang.ref.SoftReference;
//VM options: -Xms5m -Xmx10m
public class SoftRefenenceDemo {

    public static void main(String[] args) {
        softRefMemoryEnough();//内存够用时软引用的处理

        System.out.println("------内存不够用的情况------");

        softRefMemoryNotEnough();//内存不足时软引用的处理
    }

    /**
     * 内存够用时软引用的处理
     */
    private static void softRefMemoryEnough() {
        Object o1 = new Object();
        //使用软引用变量引用 o1
        SoftReference<Object> s1 = new SoftReference<Object>(o1);
        System.out.println(o1);
        System.out.println(s1.get());
        
        //将强引用去除
        o1 = null;
        
        System.gc();

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

     /**
      * 内存不足时软引用的处理
      *
     * JVM配置`-Xms5m -Xmx10m` ,然后故意new一个一个大对象,使内存不足产生 OOM,看软引用回收情况
     */
    private static void softRefMemoryNotEnough() {
        Object o1 = new Object();
        //使用软引用变量引用 o1
        SoftReference<Object> s1 = new SoftReference<Object>(o1);
        System.out.println(o1);
        System.out.println(s1.get());
        
        //将强引用去除
        o1 = null;

        try {
            //创建10M对象
            byte[] bytes = new byte[10 * 1024 * 1024];
        }catch (OutOfMemoryError e) {
           e.printStackTrace();
        }

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

执行结果:
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第6张图片

可以很清楚的看到当内存不足时,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉,如果干掉软引用的包裹的对象后内存还是不足就会抛出OOM, 也就是只有在内存不足,JVM才会回收该对象

此外,软引用还可以附带一个引用队列ReferenceQueue,当对象可达性发生改时(由可达变为不可达,被回收),此时软引用进入队列,通过这个引用队列,可以跟踪对象的回收情况

软引用到底有什么用呢?

  • 根据软引用特性,非常适合保存一些可有可无的缓存,如果这么做,当系统内存空间不足时,会及时回收他们,不会导致内存溢出,当系统内存资源充足时,这些还存有可以存在相当长的时间,提升系统速度。

3.弱引用

  • 弱引用也是用来描述非必需对象的,它的强度比软引用更弱一些被弱引用变量引用的对象只能生存到下一次垃圾收集发生之前。 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

  • Java1.2后用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。

    弱引用的使用和软引用类似,使用WeakReference包裹对象即可:

  • 对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存。

import java.lang.ref.WeakReference;

public class WeakReferenceDemo {
    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference<Object> w1 = new WeakReference<Object>(o1);

        System.out.println(o1);
        System.out.println(w1.get());
        
        //将强引用去除
        o1 = null;
        System.gc();

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

执行结果:
在这里插入图片描述

可以很清楚的看到明明内存还很充足,但是触发了GC,资源还是被回收了。弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap

使用WeakHashMap

public class WeakHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
        myHashMap();
        System.out.println("-------------------");
        myWeakHashMap();
    }

    /**
     * 测试GC HashMap
     */
    public static void myHashMap() {
        HashMap<String, String> map = new HashMap<String, String>();
        String key = new String("k1");
        String value = "v1";
        map.put(key, value);

        System.out.println(map);

        key = null;
        System.gc();
        System.out.println("GC——>HashMap");

        System.out.println(map);
    }

    /**
     * 测试GC 弱引用WeakHashMap
     * @throws InterruptedException
     */
    public static void myWeakHashMap() throws InterruptedException {
        WeakHashMap<String, String> map = new WeakHashMap<String, String>();
        //String key = "weak";
        // 刚开始写成了上边的代码
        //思考一下,写成上边那样会怎么样? 那可不是引用了
        String key = new String("weak");
        String value = "map";
        map.put(key, value);

        System.out.println(map);

        //去掉强引用
        key = null;

        System.gc();
        System.out.println("GC——>WeakHashMap");

        Thread.sleep(1000);
        System.out.println(map);
    }
}

执行结果:
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第7张图片
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第8张图片

4.引用队列

ReferenceQueue 是用来配合引用工作的,没有ReferenceQueue 一样可以运行。

ReferenceQueue是专门用来存放引用的, 当软引用,弱引用,虚引用对应的那个对象被回收后的同时,该引用会自动加入到你所定义的ReferenceQueue中.

  • SoftReferenceWeakReferencePhantomReference 都有一个可以传递 ReferenceQueue 的构造器。

  • 创建引用的时候,可以指定关联的队列当 GC 释放对象内存的时候,会将引用加入到引用队列。 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动,这相当于是一种通知机制。

  • 当关联的引用队列中有数据的时候,意味着指向的堆内存中的对象被回收。通过这种方式,JVM 允许我们在对象被销毁后,做一些我们自己想做的事情。

【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第9张图片

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

public class TestReferenceQueue {
    public static void main(String[] args) {
        ReferenceQueue rq = new ReferenceQueue();
        WeakReference wr = new WeakReference(new TestReferenceQueue(), rq);
        System.out.println("弱引用对应的对象:" + wr.get() + ", 弱引用本身:" + wr);
        System.out.println("队列中对象:" + rq.poll());
        /**
         * TestReferenceQueue中的对象只有一个引用 就是wr弱引用
         * 因此直接调用gc就可以
         */
        System.gc();
        System.out.println("弱引用对应的对象:" + wr.get() + ", 弱引用本身:" + wr);
        System.out.println("队列中对象:" + rq.poll());
    }
}

//弱引用对应的对象:com.demo.reference.TestReferenceQueue@3e57cd70, 弱引用本身:java.lang.ref.WeakReference@9a7504c
//队列中对象:null
//弱引用对应的对象:null, 弱引用本身:java.lang.ref.WeakReference@9a7504c
//队列中对象:java.lang.ref.WeakReference@9a7504c

执行结果
在这里插入图片描述
在弱引用对应的对象被回收后该弱引用对象本身也进入到了ReferenceQueue中,ReferenceQueue清除失去了弱引用对象的弱引用本身, 软引用,虚引用也是如此.

5.虚引用(对象回收和跟踪)

虚引用也称为 “幽灵引用” 或者 “幻影引用” ,它是最弱的一种引用关系。

  • 虚引用,顾名思义,就是形同虚设,与其他几种引用都不太一样,一个对象被虚引用变量所引用的,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

  • Java1.2后用java.lang.ref.PhantomReference 来实现。

  • 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(RefenenceQueue)联合使用。

虚引用的特点之一:虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。

  • 虚引用的主要作用是跟踪对象垃圾回收的状态。仅仅是提供了一种确保对象被finalize 以后,做某些事情的机制。

  • PhantomReferenceget方法总是返回 null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入 finalization 阶段,可以被 GC 回收,用来实现比 finalization 机制更灵活的回收操作。

    虚引用特点之儿:无法通过虚引用来获取对一个对象的真实引用

    • 换句话说,设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceDemo {

    public static void main(String[] args) throws InterruptedException {
        //声明强用对象
        Object o1 = new Object();
        //声明引用对象
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        //虚引用中配置引用对象和引用队列
        PhantomReference<Object> phantomReference = new PhantomReference<Object>(o1, referenceQueue);

        System.out.println(o1);//null
        System.out.println(referenceQueue.poll());//null
        System.out.println(phantomReference.get());//null

        o1 = null;  
        System.gc();
        
        Thread.sleep(3000);

        System.out.println(o1);//null
        System.out.println(referenceQueue.poll()); //引用队列中  java.lang.ref.PhantomReference@6e2c634b
        System.out.println(phantomReference.get()); // null
    }
}

执行结果
【Java基础】理解强引用、软引用、弱引用、虚引用、引用队列_第10张图片
当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。

虚引用有什么用呢?

  • 由于虚引用可以跟踪对象的回收时间,所以可以将一些资源的释放操作放置在虚引用中执行和记录

Java 允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。(但不推荐在代码中使用)

6.总结

引用类型 被回收时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止运行时
软引用 内存不足时 对象缓存 内存不足时
弱引用 jvm垃圾回收时 对象缓存 gc运行后
虚引用 未知 未知 未知

在实际程序设计中一般很少使用弱引用与虚引用使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生

  • 利用软引用和弱引用解决OOM问题:假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。

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

Reference源码(JDK8)

你可能感兴趣的:(Java基础)