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<Bitmap> 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<Bitmap> 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.弱引用

弱引用(WeakReference)是弱于软引用的引用类型,与软引用类似,不同的是弱引用不能阻止垃圾回收,在垃圾回收机制运行时,如果一个对象的引用是弱引用的话,不管内存空间是否足够,对象都会被回收。弱引用常常被用于防止内存泄漏,最常见的是单例和Handler造成的内存泄漏。

//弱引用实例
WeakReference<Context> 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无法销毁回收,就会导致内存溢出,我们用图形来说明一下:

那么我们可以在InstanceClass构造的时候将Activity的引用存入到弱引用中,需要使用的时候再出来;

public class InstanceClass {
    private Context mContext;
    private static InstanceClass mInstanceClass;

    public InstanceClass(Context context) {
        //构造弱引用实例,将context存入弱引用中
        WeakReference<Context> 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<Object> queue = new ReferenceQueue<>();
//虚引用
PhantomReference<Object> phantomReference = new PhantomReference<Object>(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如下:

虚引用无法通过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<String> 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 既持有强引用,也持有弱引用,当然不会被回收;将打印数据如下:

在Andorid中(Activity)中执行上面的代码,那么打印的都是value,因为System.gc()只是通知系统去垃圾回收,没有立即去回收,并不知道它什么时候回收。

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。

我是suming,感谢各位的支持和认可,您的点赞、评论、收藏【一键三连】就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !

要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!

源码地址:https://github.com/FollowExcellence/AndroidOptimizeDemo

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值