Android内存笔记之内存泄漏

Android内存笔记之内存泄漏

一、什么是内存泄露?

1、内存回收

Java的特点让我们不用通过malloc去申请内存,也不用主动释放,Java会帮我们去回收不再使用的内存,这个过程叫做GC。

那虚拟机怎么知道哪些对象该被回收,哪些对象该被回收呢?虚拟机采用的是可达性策略。

虚拟机会定义一系列的GC Root,如果一个对象是从GC Root出发可达的,那就不进行回收。如果这个对象从任何GC Root出发都不可达,虚拟机就会认为这是一个不再使用的对象,进行回收。
在这里插入图片描述

如图中,a和b都是可回收的对象。

2、哪些是GC Root?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象

具体讲解可参考https://blog.csdn.net/weixin_38007185/article/details/108093716。

3、内存泄漏

来一个比较有画面的比喻:

有一家很火爆的餐厅,他的座位是有限的。餐厅派了个人来巡逻,当座位不够的时候,就巡逻一遍看看有没有人吃完了还不走,就赶走他们把位置让出来给还在排队的顾客。(这个过程就是GC)。但如果有人吃完了,但巡逻的人也没有把他们赶走,那这个位置就被浪费了。

餐厅就是设备,座位就是内存,顾客就是对象。当对象不再使用,但是还占用着内存不释放,导致这块内存不可用,就把这样的现象称为内存泄漏。

4、为什么会内存泄露?

通过GC回收策略我们可以知道,决定一个对象是否回收是通过看从GC Root可不可达。那泄漏的对象就是被GC Root直接或间接引用了,导致虚拟机以为他还在使用,就不会进行回收。

二、常见的Activity内存泄漏

1、Handler导致Activity泄漏

泄漏原因

大家一定在各种地方看见过说匿名内部类Handler容易导致Activity泄漏,因为匿名内部类持有了外部类的引用。

但这种说法容易造成误解:认为匿名内部类都会造成内存泄漏。这种说法是不全面的。

导致泄漏,一定是该被回收却没被回收,那就要分析这里为什么Activity得不到回收。

Handler会造成Activity泄漏的常见场景就是通过Handler发送的Message还在MessageQueue里,也就是这条消息还没得到执行。

此时的MessageQueue就持有了这一条Message,而通过Handler的sendMesesage源码分析可以知道,最终是走到了enqueueMessage()这个方法

	private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

通过msg.target = this可以知道,这一条Message持有了Handler。而Handler作为匿名内部类又持有了Activity,于是就产生了这样一条引用链:

主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

这里的threadlocal是不会被gc回收的,所以导致了activity无法被回收,所以才导致了泄漏。

解决办法

解决办法有两个:

  1. 将handler定义为静态匿名内部类,然后就Activity作为弱引用持有。
  2. 在Activity被销毁时,清空Handler发送的Message。

2、静态类持有Activity或Context导致的内存泄漏

泄漏原因

静态类因为其生命周期与应用周期一样长,所以如果静态类直接或间接持有了Activity的引用,也会导致内存泄漏。

2.1 单例模式

单例模式的实现基本都是static,所以生命周期也和应用周期一样长。

2.2 非静态内部类的静态实例

看名字有一点绕,其实大概就是下面这种情况:

public class TTestAActivity extends AppCompatActivity{

     private static Handler mHandler = null;


     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          mHandler = new Handler(Looper.getMainLooper()) {
               @Override
               public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
               }
          };
     }
}

这时候handler作为匿名内部类持有了外部类的引用,但他又是个静态变量,就会导致Activity泄漏。

解决办法

不要让静态类长期持有一些数据,在不用的时候及时置空。也不要使用非静态内部类的静态实例。

3、广播接收器未注销导致的内存泄漏

泄漏原因

在学习广播的使用的时候,就看到有多次强调注册了广播接收器,需要在销毁的时候进行unregisterReceiver()。注册后的广播接收器会有这样一条引用链:

Application -> LoadApk -> BroadcastReceiver

此时如果没有及时unregisterReceiver(),就会造成广播接收器泄漏。如果这个BroadcastReceiver还是一个Activity里的内部类,那么他就持有了Activity,进而也会导致Activity的泄漏。

解决办法

对于不在使用范围内的广播要及时进行注销。

4、资源使用未关闭

泄漏原因

这里的资源包括文件流InputStream, OutputSteam,文件File、数据库游标Cursor、图像Bitmap。这些资源如果没有及时关闭,那么系统就不会进行回收,进而导致Activity泄漏。

解决办法
// 对于 文件流File:关闭流
InputStream / OutputStream.close()
// 关闭文件
file.close()
// 关闭游标
cursor.close()
// 回收bitmap占用的内存
bitmap.recycle()
bitmap = null

三、总结

对于泄漏问题的分析是一定要抓住关键点,就是该被回收的没被回收,没被回收一定从GCRoot出发不该有的引用链。

抓住了这个重点,那内存泄漏的问题分析主要就是找出这一条不合理的引用链了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值