Java 中的 强引用、软引用、弱引用 和 虚引用

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/yichen97/article/details/91488300

引言

Java中的强引用、软引用、弱引用和虚引用统称为Java四种引用方式。

了解四种引用方式是用代码的方式去控制Java对象的生命周期,以达到节省资源等目的。

强引用(StrongReference)

强引用是最最普遍的一种引用了,也是最不容易被回收的一种对象。

Object obj = new Object();

只要这个对象还存在强引用,垃圾收集器就永远不会回收掉被引用的对象;即使抛出OutOfMemoryError错误,也依然不会被回收。

那既然是最普遍的,又是最难回收的,那岂不是很容易就抛出OutOfMemoryError?

引用保存在栈中,对象保存在堆中,当包含这个对象的方法执行完毕后,会退栈,该对象的引用也就不存在了,这种情况下,该对象就会被回收;

如果该对象为全局时,为了方便回收,需要在这个对象不用的时候给该对象赋值为 NULL,因为有强引用的对象不会被垃圾回收器回收。

在源码:/libcore/ojluni/src/main/java/java/util/ArrayList.java

为了方便回收,和避免后续调用add()方法添加元素时进行重新内存分配,源码中的操作就是将 elementData 中的每一位都置为了空。

软引用(SoftReference)

软引用被表示为还需要,但并非必需的对象。所以在一般情况下,被用在内容敏感的高速缓存中,比如说各种网页、图片缓存。

在系统将要发生内存溢出之前,会先将软引用的对象列入回收范围,以此来避免OutOfMemoryError的抛出。但也并不是所有只被软引用所引用的对象都会被回收,Java虚拟机会优先回收闲置时间长的软可及对象,尽量放过刚刚被创建的新的软可及对象。但是如果将所有的软引用对象都回收了,仍不能满足足够的内存需求,则抛出OutOfMemoryError。

要使用SoftReference类来实现。

在SoftReference保存了一个对象的软引用之后,假如说垃圾对象还未被回收之前,SoftReference类所提供的get()方法所返回的是该对象的强引用;但如果对象被回收之后,get()方法所返回的为null。

            //mObject 对 MyObject 的强引用
            MyObject mObject = new MyObject();
            //来自 SoftReference对象 的软引用
            SoftReference mSoftReference = new SoftReference(mObject);
            //结束 mObject 对 MyObject的强引用
            //这时候MyObject为软引用,如果这时候执行GC,该对象就可能会被回收。
            mObject = null;

此时,get()方法的具体写法为:

     MyObject myObject = (MyObject)mSoftReference.get();

 使用ReferenceQueue清除已经失去了软引用对象的SoftReference:

当对象被回收之后(mSoftReference.get()的返回值为0),所new出来的SoftReference就跟着没有什么意义了;相反,假如说有很多很多软引用的对象被回收之后,就会存在大量的SoftReference对象,这些对象的遗留并不会有什么用了,却可能会带来内存泄露。

所以在创建软引用的对象时:

      ReferenceQueue queue = new ReferenceQueue();
      SoftReference ref = new SoftReference(mObject,queue);

对象SoftReference的第二个参数可以理解为一个队列。

当SoftReference所软引用的mObject被垃圾回收器回收的时候,SoftReference对象就已经失去了存在的意义,这时候,SoftReference对象就被列进了ReferenceQueue这个队列。

可我们知道,我们永远也不知道垃圾回收器在什么时候会回收,所以我们需要在一个恰当的时候去检查一下,那如何判断SoftReference所软引用的对象是否已经被回收了?

检查软引用对象是否被回收,对象ReferenceQueue提供了一个poll方法。

ReferenceQueue调用poll(),若队列为空,则直接返回NULL;若队列不为空,队列中前面的一个Reference对象。

软引用的作用还主要体现在缓存功能上,当内存足够的时候,直接通过软引用拿值,不需要从真实数据中拿值;当内存不足时,自动删除这部分的缓存数据,从真实的数据中查询。

弱引用(WeakReference)

弱引用与软引用差不多,都是用来描述非必要对象的;但最大的弱引用不论内存是否充足,在垃圾回收时都会回收只被弱引用关联的对象。

import java.lang.ref.WeakReference;

public class ReferenceDemo {

    public static void main (String[] args){

        //所引用的对象只与弱引用相关联
        WeakReference<String> wr = new WeakReference<String>(new String("hello"));
        System.out.println(wr.get());
        System.gc();
        //当gc操作之后,该引用的对象就被回收了
        System.out.println(wr.get());
    }
}

输出结果:

如果将这个对象与强引用再与之关联,能否被回收呢?

import java.lang.ref.WeakReference;

public class ReferenceDemo {

    public static void main (String[] args){

        //将hello先与str有一个强引用
        String str = new String("hello");
        //来自 WeakReference对象 的弱引用
        WeakReference<String> wr = new WeakReference<String>(str);
        System.out.println(wr.get());
        System.gc();
        System.out.println(wr.get());
    }
}

运行结果:

这就表明该对象与强引用一旦关联,则gc回收时也不会回收该对象。

弱引用与软引用一样,也可以与一个引用队列联合使用,如果被弱引用所引用的对象被回收,这个弱引用就会被加入到与之相关联的引用队列中去。

虚引用(PhantomReference)

虚引用的引用弱的就像是没有被关联引用一样,在任何时刻都可能被垃圾回收器回收掉。使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。

虚引用必须和引用队列相关联使用,也就是说在声明一个虚引用时,是必须传入一个ReferenceQueue的。当垃圾回收器准备回收一个对象时,如果发现他还有一个虚引用,就会把这个虚引用加入到与之关联的引用队列中去;而在其关联的虚引用出队前,不会彻底销毁该对象。

与软引用和弱引用不同,显式使用虚引用可以阻止对象被清除,只有在程序中显式或者隐式移除这个虚引用时,这个已经执行过finalize方法的对象才会被清除。想要显式的移除虚引用的话,只需要将其从引用队列中取出然后扔掉(置为null)即可。

对象的可及性判断

某个对象的可达性如何判断?

单条引用路径的可达性:在这条路径中,最弱的一个引用决定对象的可达性。

多条引用路径的可达性:n条路径中,最强的一条引用决定对象的可达性。

对于对象3:

路径(1 - 3)中取最弱的引用 3,因此该路径对 对象5 的引用为软引用;

路径(2 - 4)中取最弱的引用 4,因此该路径对 对象5 的引用为弱引用;

在这两条路径之间取最强的引用,于是该对象5是一个软可达对象。

利用软引用和弱引用避免OOM问题

试想一个场景:一个应用需要读取大量的本地图片(就像某购物平台首页),实现这个功能,我们既可以每次读取图片时都从硬盘中获取,也可以全部加在到内存中,需要时直接从内存中读取。

但是,如果每次读取图片都从硬盘中读取,则会严重影响性能,毕竟性能的消耗大多是在这种读写操作中消磨的;

如果将图片全部加载到内存中,又有可能造成内存溢出的危险。

设计思路:

用一个HashMap来保存 图片的路径相应图片对象所关联的软引用 之间的映射关系。

当内存不足的时候,JVM会自动回收这些缓存图片对象所占用的空间,从而有效得比米娜了OOM的问题。

实现:

1. 定义一个HashMap,用来保存软引用的对象。

key:图片的路径;

value:图片对象所关联的软引用。

    private Map<String,SoftReference<Bitmap>> imageCache = new HashMap<String,SoftReference<Bitmap>>();

2. 保存Bitmap的软引用到HashMap。

    public void addBitmapToCache(String path){

        //现将路径转化为bitmap对象(强引用)
        Bitmap bitmap = BitmapFactory.decodeFile(path);

        //来自softBitmap对象 对 Bitmap对象的软引用
        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);

        //将来自softBitmap对象添加至Map使其缓存
        imageCache.put(path,softBitmap);
    }

3. 使用SoftReference的get()方法可拿到Bitmap对象。

    public Bitmap getBitmapByPatch(String patch){
        
        //先从缓存中取出被软引用的Bitmap对象
        SoftReference<Bitmap> softmap = imageCache.get(patch);
        
        //必须判断软引用是否存在,否则调用它的get方法可能NullPointerException错误
        //若软引用不存在,直接返回null
        if(softmap == null){
            return null;
        }
        
        //取出Bitmap对象,若内存不足,Bitmap将被回收,取得空
        Bitmap bitmap = softmap.get();
        return bitmap;
    }

这样做的好处:

在OOM异常发生之前,这些缓存的图片可以被释放掉,从而避免内存达到上限,导致Crash。

在垃圾回收器对这个对象回收之前,SoftReference.get()会返回该对象的强引用;一旦被回收之后,该方法会返回null。

在什么时候使用软引用或者弱引用?

1. 如果只需避免OutOfMemoryError,使用软引用;如果想尽快回收对象,则使用弱引用。

2. 所引用的对象若在以后经常使用,则尽量使用软引用,一直读取缓存,速度快;若在以后使用概率较低,可以使用弱引用,将不使用的对象尽快回收。

参考文献

https://www.cnblogs.com/huajiezh/p/5835618.html

https://www.cnblogs.com/yw-ah/p/5830458.html

https://www.jianshu.com/p/825cca41d962

https://cloud.tencent.com/developer/article/1045143

https://cloud.tencent.com/developer/article/1192550

https://cloud.tencent.com/developer/article/1330173

https://www.jianshu.com/p/86efa167a627

https://in355hz.iteye.com/blog/1923393

https://www.cnblogs.com/mfrank/p/9837070.html

猜你喜欢

转载自blog.csdn.net/yichen97/article/details/91488300
今日推荐