Android的四种引用(强引用、弱引用、软引用、虚引用)

前言:满纸荒唐言,一把辛酸泪;都云作者痴,谁解其中味。
一、概述 早在JDK1.2就把对象引用分为四种级别,从而使程序能更灵活控制它的生命周期,级别由高到底依次为:强 > 软 > 弱 > 虚引用。而GC垃圾回收器(Garbage Collection)对不同的类型有着不同的处理方法,了解这些处理方式有助于我们写出更高质量的代码。
在Java中,一切被视为对象,引用则是用来操纵对象的。对象和引用之间的关系可以用遥控器(引用)操控电视机(对象)这个场景来理解。遥控器只要链接电视机那么就能操控电视机,当我们尝试操控一个未指向任何对象的引用去操作对象时,就会出现空指针异常(NullPointerException),就好比遥控器没有链接任何电视机,你却去操控遥控器,那是没法操控的。
Java有一个重要的有点就是通过垃圾回收机制自动管理内存回收,开发者不需要调用函数来释放内存,内存的分配是由程序分配的,而内存的回收由内存来完成。GC为了释放对象,会监控每一个对象的运行状态,包括申请,引用,被引用和赋值,GC都需要监控。监控对象的状态是为了更加准确地,及时地释放对象,释放对象的原则是该对象不再被引用。但是由于我们因为各种原因导致一些对象使用完后扔被持有引用而没有被回收导致内存泄漏。
总结了几个引用,下面我们在一一细讲:
引用类型 说明 使用场景
强引用(StrongReference) 不会自动回收,最难被GC回收的,宁可抛出异常也不回收强引用指向的对象 任何场景
软引用(SoftReference) 内存不足时,GC会回收软引用指向的对象 比较少使用,已被LruCache替代
弱引用(WeakReference) 不管内存足不足,只要GC了都能回收弱引用指向的对象 常用于避免内存泄漏
虚引用(PhantomReference) 随时都能回收,也称幽灵引用,相当于没有指向任何实例引用 跟踪对象是否被回收,很少使用
二、引用详解 1.强引用
强引用(StrongReference)是使用最普遍的引用,如果一个对象属于强引用,不会自动被回收,当内存不足时,jvm宁愿抛出OOM使程序异常也不会回收强引用。当我们使用一个new关键字去创建对象的时候,这个对象的引用就是强引用。
//str表示强引用,指向new String()这个对象 String str = new String();

2.软引用
软引用(SoftReference)是除了强引用外,最强的引用类型。如果系统内存足够是不会回收软引用指向的对象的,如果系统内存不足那么就会回收。使用软引用,在OutOfMemory异常发生前,软引用指向的对象就可以被释放掉,避免内存达到上限避免crash的发生。
//创建软引用实例,将bitmap存入到软引用中 SoftReference softReference = new SoftReference<>(bitmap); Bitmap bitmap = softReference.get()if ("内存不足"){ //将软引用中的对象设置为null,否则既持有强引用也持有软引用是无法回收的 bitmap = null; //通知系统回收 System.gc(); }

注意:软引用是在内存不足的时候才会被回收,我们调用System.gc()只是起到通知的作用,JVM什么时候扫描回收对象是它自己的态度决定,就算扫描到软引用也不一定会回收它,只有在内存不足的时候才回收。
我们尝试使用软引用来保存图片实例;
ImageView imageView = findViewById(R.id.iv_soft_reference); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_round); //创建软引用实例,将bitmap存入到软引用中 SoftReference softReference = new SoftReference<>(bitmap); if (softReference.get() != null) { imageView.setImageBitmap(softReference.get()); }

通过软引用的get()方法获取强Bitmap对象实例的引用,如果对象未被回收那么设置图片到控件上。如果内存紧张,系统就会GC,softReference.get()就不会反悔Bitmap对象,而是反回null,这里也需要把软引用获取的引用做空判断,避免空指针。
实际情况中,使用软引用虽然可以避免OOM,但是不适用于某些场景,使用的比较少。在Android开发中,一种更好的选择是LruCache,LRU是Least Recently Used的缩写,即“最近最少使用”的意思,它内部会维护一个固定大小的内存,当内存不足时,会把最近最少使用的数据移除掉。
3.弱引用
【Android的四种引用(强引用、弱引用、软引用、虚引用)】弱引用(WeakReference)是弱于软引用的引用类型,与软引用类似,不同的是弱引用不能阻止垃圾回收,在垃圾回收机制运行时,如果一个对象的引用是弱引用的话,不管内存空间是否足够,对象都会被回收。弱引用常常被用于防止内存泄漏,最常见的是单例和Handler造成的内存泄漏。
//弱引用实例 WeakReference weakReference = new WeakReference<>(context); //获取弱引用保存的引用 Context ctx = weakReference.get();

注意:如果一个对象比较少使用,并且洗碗在使用是随时就获取到,又不想影响次对象的垃圾回收,你可以用WeakReference来标记此对象。
我们来看看单例造成的内存泄漏:
public class InstanceClass { private Context mContext; private static InstanceClass mInstanceClass; public InstanceClass(Context context) { this.mContext = context; }//传入的Context如果外部类(比如Activity)需要销毁时,InstanceClass仍然持有Context,导致Activity无法销毁回收 public static InstanceClass getInstance(Context context) { if (mInstanceClass == null) { mInstanceClass = new InstanceClass(context); } return mInstanceClass; } }

在Activity中使用该实例:
InstanceClass instanceClass = InstanceClass.getInstance(this);

单例是开发中比较常用的设计模式,但是使用不当也会造成内存泄漏,如上面例子,InstanceClass通过构造方法持有外部类Activity的引用,因为InstanceClass中的实例是static修饰的,所以InstanceClass的声明周期时和应用声明周期一致,如果当外部类销毁的时候,InstanceClass仍然持有Context,导致Activity无法销毁回收,就会导致内存溢出,我们用图形来说明一下:
Android的四种引用(强引用、弱引用、软引用、虚引用)
文章图片

那么我们可以在InstanceClass构造的时候将Activity的引用存入到弱引用中,需要使用的时候再出来;
public class InstanceClass { private Context mContext; private static InstanceClass mInstanceClass; public InstanceClass(Context context) { //构造弱引用实例,将context存入弱引用中 WeakReference mWeakReference = new WeakReference<>(context); //从弱引用中获取,mWeakReference.get()可能为空,需要做空判断 this.mContext = mWeakReference.get(); }//传入的Context如果外部类(比如Activity)需要销毁时,InstanceClass仍然持有Context,导致Activity无法销毁回收 public static InstanceClass getInstance(Context context) { if (mInstanceClass == null) { mInstanceClass = new InstanceClass(context); } return mInstanceClass; } }

如果Activity被回收了那么mWeakReference.get()就会返回null,这里需要做空判断。
4.虚引用
虚引用(PhantomReference)是最弱的引用,一个持有虚引用的对象和没有引用几乎是一样的,随时都可能被垃圾回收器回收。通过虚引用的get()方法获取到的引用都会失败(为null),虚引用必须和引用队列ReferenceQueue一起使用。
ReferenceQueue引用队列作用在于跟踪垃圾回收过程。当垃圾回收器回收对象时,如果发现它还有虚引用,就会在回收后销毁这个对象,并且将虚引用指向的对象加入到引用队列。只能通过虚引用是否被加入到ReferenceQueue来判断虚引用是否为GC回收,这也是判断对象是否为回收的唯一途径。
Java的Object类中有finalize()方法,原理:一旦垃圾回收器准备释放对象占用的内存空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存,但是问题在于,虚拟机不能保证finalize()何时被调用,因为GC运行时间不是固定的。
使用虚引用就能解决这个问题,虚引用主要用于跟踪垃圾被回收的活动,主要用来实现比较精细的内存使用控制,这对Android来说很有意义,比如我们可以在确定一个Bitmap被回收后,再去申请另个Bitmap的内存,通过这种方式可以使程序消耗的内存维持在一个相对较低较稳定的水平。
//引用队列 ReferenceQueue queue = new ReferenceQueue<>(); //虚引用 PhantomReference phantomReference = new PhantomReference(new Object(), queue); Log.e(TAG, "虚引用:PhantomReference == " + phantomReference.get()); //系统垃圾回收 System.gc(); System.runFinalization(); Log.e(TAG, "虚引用:PhantomReference == 是否被回收:" + (queue.poll() == phantomReference) + " | 队列中的PhantomReference == " + queue.poll());
phantomReference.get()获取的引用一直为null,调用系统回收垃圾,queue.poll()获取保存的引用对象,并且把它在这个队列中移除,打印log如下:
Android的四种引用(强引用、弱引用、软引用、虚引用)
文章图片

虚引用无法通过get()方法获取目标的引用,一直都是返回null,可以看源码:
public T get() { return null; }

三、回收验证 上面我们验证了虚引用的垃圾回收,我们这次来验证一下强引用、软引用和弱引用的垃圾回收。这里我们使用System.gc(),如果在Android中使用这个API的话,系统只是通知GC,但是什么时候GC我并不知道,所以我们要把例子放在Java的main方法中执行,因为Java和Android的垃圾收集器是不同的,Android不是使用标准的JVM,而是使用Dalvik VM。
1.弱引用垃圾回收验证
在main方法中执行:创建强引用对象,将引用存放到弱引用中,然后手动调用系统gc;
public static void main(String[] args){ String str = new String("value"); WeakReference weakReference = new WeakReference<>(str); str = null; System.out.println("弱引用:WeakReference == GC回收前:" + weakReference.get()); //系统GC垃圾回收 System.gc(); System.out.println("弱引用:WeakReference == GC回收后:" + weakReference.get()); }

这里需要str = null; 如果不设置那么结果返回的都是value,为什么呢?因为如果你不将str = null,那么对象str 既持有强引用,也持有弱引用,当然不会被回收;将打印数据如下:
Android的四种引用(强引用、弱引用、软引用、虚引用)
文章图片

在Andorid中(Activity)中执行上面的代码,那么打印的都是value,因为System.gc()只是通知系统去垃圾回收,没有立即去回收,并不知道它什么时候回收。
Android的四种引用(强引用、弱引用、软引用、虚引用)
文章图片

至此,本文结束!

源码地址:https://github.com/FollowExcellence/AndroidOptimizeDemo
请尊重原创者版权,转载请标明出处: https://blog.csdn.net/m0_37796683/article/details/103169563 谢谢!

    推荐阅读