原创

java强引用、软引用、弱引用、虚引用的概念理解

基本概念

从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。下面来阐述一下这四种类型引用的概念:
file

file

1、强引用(StrongReference)

强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:

Object object = new Object();
String str = "hello";

只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。比如下面这段代码:

public class Main {
    public static void main(String[] args) {
        new Main().fun1();
    }

    public void fun1() {
        Object object = new Object();
        Object[] objArr = new Object[1000];
    }
}

当运行至Object[] objArr = new Object[1000];这句时,如果内存不足,JVM会抛出OOM错误也不会回收object指向的对象。不过要注意的是,当fun1运行完之后,object和objArr都已经不存在了,所以它们指向的对象都会被JVM回收。
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

比如Vector类的clear方法中就是通过将引用赋值为null来实现清理工作的:
file

2、软引用(SoftReference)

软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。

软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用

import java.lang.ref.SoftReference;

public class Main {
    public static void main(String[] args) {
        // 获取页面进行浏览,Browser虚拟示例对象,实际不存在
        Browser prev = new Browser();               
        // 浏览完毕后置为软引用    
        SoftReference<Browser> sr = new SoftReference<>(prev);
        // 还没有被回收器回收,直接获取
        if(sr.get()!=null){ 
            rev = (Browser) sr.get();
        }else{
            // 由于内存吃紧,所以对软引用的对象回收,重新构建
            sr = new SoftReference(new Browser());       
        }
    }
}

3、弱引用(WeakReference)

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

如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。

当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

下面的代码会让str再次变为一个强引用:

String str=new String("abc");    
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
String abc = abcWeakRef.get();
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个弱引用就会被加入到与之关联的引用队列中。
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.LinkedList;

public class ReferenceTest {

    private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<>();

    public static void checkQueue() {
        Reference<? extends VeryBig> ref;
        // 获取gc回收信息引用队列中的弱引用
        while ((ref = rq.poll()) != null) {
            System.out.println("In queue: "    + ((VeryBigWeakReference) (ref)).id);
        }
    }

    public static void main(String[] args) {
        int size = 3;
        // 创建3个VeryBigWeakReference弱引用的VeryBig
        LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<>();
        for (int i = 0; i < size; i++) {
            // 创建弱引用对象,并指定回收引用队列
            weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq));
            System.out.println("Just created weak: " + weakList.getLast());
        }
        // 通知系统gc回收
        System.gc();
        try {
            // 下面休息6秒,让上面的垃圾回收线程运行完成(其实不需要,就是保证准确)
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 查看ReferenceQueue储存的信息
        checkQueue();
    }
}

/**
 * 弱引用对象
 */
class VeryBig {

    public String id;

    // 占用空间,让线程进行回收
    byte[] b = new byte[2 * 1024];

    public VeryBig(String id) {
        this.id = id;
    }

    /**
     * gc收回时会自动调用finalize方法
     */
    @Override
    protected void finalize() {
        System.out.println("Finalizing VeryBig " + id);
    }
}

/**
 * 创建指定功能的弱引用类
 */
class VeryBigWeakReference extends WeakReference<VeryBig> {

    public String id;

    /**
     * 创建弱引用对象,并指定回收引用队列
     * @param big   弱引用对象
     * @param rq    回收引用队列
     */
    public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) {
        super(big, rq);
        this.id = big.id;
    }

    /**
     * gc收回时会自动调用finalize方法
     */
    @Override
    protected void finalize() {
        System.out.println("Finalizing VeryBigWeakReference " + id);
    }
}

执行结果:
file

4、虚引用(PhantomReference)

如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;


public class Main {
    public static void main(String[] args) {
        // 创建引用队列
        ReferenceQueue<String> queue = new ReferenceQueue<>();
        // 创建虚引用并指定引用队列
        PhantomReference<String> pr = new PhantomReference<>(new String("hello"), queue);
        System.out.println(pr.get());
    }
}
正文到此结束
本文目录