《深入理解java虚拟机v3》Java 关于强引用,软引用,弱引用和虚引用的区别与用法

背景

《深入理解java虚拟机v3》3.2.3章节谈到引用类型,分为强引用,软引用,弱引用和虚引用,但是没有给出详细的例子,特别是对于这些不同的引用类型的使用场景更是疑惑。

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

1、强引用(StrongReference)

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:

Object o=new Object();   //  强引用

当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:

o=null;     // 帮助垃圾收集器回收此对象

显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。

回收内存原理举例

public void test(){
    
    
    Object o=new Object();
    // 省略其他操作
}

在一个方法的内部有一个强引用,这个变量o引用一个Object实例,o保存在栈中,而真正的被引用的内容(Object)保存在堆中。当test方法运行完成后就会退出方法栈,o被销毁,则o与Object实例之间的引用关系不复存在,这个Object会被回收。

但是如果这个o是全局变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。

栈里面的变量在退出栈时,原有的引用关系会消失,而全局变量(被static修饰)会始终存活,引用关系不会自动消亡。

强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:

private transient Object[] elementData;
public void clear() {
    
    
    modCount++;
    // Let gc do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;  //删除引用关系
    size = 0;
}

ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null,变量elementData与数组实例之间的强引用仍然存在,与elementData[i] = null相比,避免在后续调用 add()等方法添加元素时进行重新的内存分配 。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。

elementData=null也会回收内存,与elementData[i] = null的区别在于后者会立即解除数组与数组元素之间的引用关系,回收速度快,并且不用重新分配数组实例的内存;而前者是解除变量elementData与数组实例直接的引用关系,数组实例与数组元素之间仍然强引用关系,根据gcRoot,也是符合回收条件的,只是效率低一点,并且需要重新申请数组实例的内存。

2、软引用(SoftReference)

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

下面是一个使用示例:

import java.lang.ref.SoftReference;

public class SoftReferenceTest {
    
    
    public static void main(String[] args)  throws InterruptedException{
    
    
        SoftReference<String> sr = new SoftReference<String>(new String("hello"));
        System.out.println(sr.get());   //打印hello
        System.gc();   //触发gc
        Thread.sleep(2000);   //gc可能存在延迟,确保下次打印之前一定先执行gc
        System.out.println(sr.get());   //打印hello
    }

}

由于gc时,内存充足,不会回收软引用,仍能从sr 中获取引用的值。

当执行gc并且内存不足时,等价于:

If(JVM.内存不足()) {
    
    
   str = null;  // 先转换为软引用
   System.gc(); // 再执行垃圾回收器进行回收
}

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

public SoftReference(T referent, ReferenceQueue<? super T> q)  //带队列的构造函数

3、弱引用(WeakReference)

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

执行gc时等价于:

str = null;  //如果是弱引用,立即解除引用关系
System.gc();
import java.lang.ref.WeakReference;

public class WeakReferenceTest {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        WeakReference<String> sr = new WeakReference<String>(new String("hello"));

        System.out.println(sr.get());     //打印hello
        System.gc(); // 通知JVM的gc进行垃圾回收

        Thread.sleep(2000); // gc可能存在延迟,确保下次打印之前一定先执行gc
        System.out.println(sr.get());   //打印null
    }
}

第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。

一个对象A可能被对象B,C同时引用,如果仅切断B与A的引用,那么A仍然会存活。

3.1弱引用被转化为强引用

弱引用可以被转化为强引用

import java.lang.ref.WeakReference;

public class WeakReferenceTest {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        WeakReference<String> sr = new WeakReference<String>(new String("hello"));

        System.out.println(sr.get());
        String abc = sr.get(); // 被改为强引用
        System.gc(); // 通知JVM的gc进行垃圾回收

        Thread.sleep(2000); // gc可能存在延迟,确保下次打印之前一定先执行gc
        System.out.println(sr.get());
    }
}

两次均打印hello,说明已经被转化为强引用,不会被回收。

3.2 弱引用可以和一个引用队列联合使用

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

WeakReference<T>(T referent, ReferenceQueue<? super T> queue)  //构造函数,第二个参数接收一个队列

当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。

这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

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<VeryBig>();

    /**
     * 打印队列
     */
    public static void checkQueue() {
    
    
        Reference<? extends VeryBig> ref = null;
        while ((ref = rq.poll()) != null) {
    
    
            if (ref != null) {
    
    
                // 打印id,验证被回收的弱引用对象自身已经被放入队列
                System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).id);
                // 验证弱引用对象引用的对象实例已经被回收
                System.out.println("In queue: " + ((VeryBigWeakReference) (ref)).get());
            }
        }
    }

    public static void main(String args[]) throws InterruptedException {
    
    
        // 构建一个弱引用数组
        int size = 3;
        LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>();
        for (int i = 0; i < size; i++) {
    
    
            weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq));
            System.out.println("Just created weak: " + weakList.getLast());

        }

        System.gc();

        // 下面休息几分钟,让上面的垃圾回收线程运行完成
        Thread.currentThread().sleep(2000);

        checkQueue();
    }
}

class VeryBig {
    
    
    public String id;
    // 占用空间2K,让线程进行回收
    byte[] b = new byte[2 * 1024];

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

    //会打印,说明 被弱引用的对象实例触发finalize
    protected void finalize() {
    
    
        System.out.println("Finalizing VeryBig " + id);
    }
}

class VeryBigWeakReference extends WeakReference<VeryBig> {
    
    
    public String id;

    public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) {
    
    
        super(big, rq);
        this.id = big.id;
    }

    // 不会打印,说明弱引用自身不会触发finalize
    protected void finalize() {
    
    
        System.out.println("Finalizing VeryBigWeakReference " + id);
    }
}

执行结果:

Just created weak: VeryBigWeakReference@68d448a1    //创建弱引用
Just created weak: VeryBigWeakReference@48ec77cb
Just created weak: VeryBigWeakReference@1cacd5d4
Finalizing VeryBig Weak 2     //被弱引用的对象实例触发finalize
Finalizing VeryBig Weak 1
Finalizing VeryBig Weak 0
In queue: Weak 2     //队列的弱引用句柄
In queue: null       //弱引用的实际对象已经被回收
In queue: Weak 1
In queue: null
In queue: Weak 0
In queue: null

上面例子只是说明一些特性,但是仍没说清楚弱引用的场景。比如放入队列有什么额外意义吗?

4、虚引用(PhantomReference)

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:`虚引用必须和引用队列 (ReferenceQueue)联合使用`。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

放入队列有什么意义?

5如何利用软引用和弱引用解决OOM问题

前面讲了关于软引用和弱引用相关的基础知识,那么到底如何利用它们来优化程序性能,从而避免OOM的问题呢?

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

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

参考:
Java 关于强引用,软引用,弱引用和虚引用的区别与用法

Java 如何有效地避免OOM:善于利用软引用和弱引用

猜你喜欢

转载自blog.csdn.net/m0_45406092/article/details/108576301