<
内存泄漏的优化
>
上一篇

线程池深入学习
下一篇

Android是如何通过Activity进行交互的

内存泄漏本身并不会造成程序异常,但是随着量的增加会导致其他各种并发症:OOM,UI卡顿

内存泄漏的优化

一、Activity内存泄漏预防

Activity承担了与用户交互的责任,因此内部需要持有大量的资源引用以及与系统交互的Context ,这会导致一个Activity对象的retained size特别大。一旦Activity因为被外部系统所持有而导致内存泄漏,被牵连导致其他对象的内存泄漏也会非常多。

造成Activity内存泄漏的场景主要有一下几种:

1.将Context或View设置为static

View默认会持有一个Context的引用,如果将其置为static将会造成View在方法区中无法被快速回收,最终导致内存泄漏。 private static ImageView imageView 。imageView会造成所在类无法被GC回收。

2.未解注册各种listener

在Activity中可能会注册各种系统监听器,比如广播

3.非静态Handler导致Activity泄漏

private Handler handler = new Handler(){
	@Override
    public void handlerMessage(Message msg){
        super.handleMesage(msg);
    }
};

上述代码中的Handler也会造成此Activity内存泄漏,一般需要将其置为static,然后内部持有一个Activity的弱引用来避免内存泄漏。

4.第三方库使用Context

经常使用第三方库时,有些库的初始化需要传入一个Context对象。但是第三方库中有可能一直持有此Context引用。如:三方库中将传入的context重新置为一个static类型。这种很难察觉,平时使用尽量使用Context.getApplicationContext,不要直接将Activity传递给其他组件 。在自己实现SDK时,可以使用context.getApplicationContext,即使外部传入是Activity,也不会造成内存泄漏context.getApplication();

二、内存泄漏检测

可以使用AndroidStudio查看Activity是否存在内存泄漏,并结合MAT来查看发生内存泄露的对象。这个今后和Android性能调优放一起学习。

除了AS之外,还有一个工具就是LeakCanary,这是一个开源库。通过它可以在APP运行时检测内存泄漏,当内存泄漏时会生成发生泄露对象的引用链,并通知程序开发人员。

可以看到LeakCanary主要分2大核心部分:

  1. 如何检测内存泄漏
  2. 分析内存泄漏对象的引用链

如何检测内存泄漏

JVM理论知识

Java中WeakReference是弱引用类型,每当发生GC时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收。如下:

public class WealRefDemo{
    public static void main(...){
        WeakReference<BigObject> reference = new WeakReference<>(new BigObject());
        
        println("before GC,reference get is " + reference.get());
        
        System.gc();
        Thread.sleep(1000);
        
        println("after GC,reference get is " + reference.get());
    }
}

结果如下

before gc,reference get is ...
after gc,reference get is null

WeakReference的构造函数可以传入ReferenceQueue,当WeakReference指向的对象被GC回收时,会把WeakReference放入ReferenceQueue中。比如上述代码中,调用WeakReference构造器时,传入一个自定义的ReferenceQueue,如下:

ReferenceQueue<BigObject> queue = new ReferenceQueue<>();
WeakReference<BigObject> reference = new WeakReference<>(new BigObject()queue);

println("before GC,queue is " + queue.poll());
println("after GC,queue is " + queue.poll());

加上打印后

before gc,queue is null
after gc,queue is ...

可以看出,当BigObject被回收之后,WeakReference会被添加到所传入的ReferenceQueue中。此时模拟一个内存泄漏。

BigObject bigObject = new BigObject();
WeakReference<BigObject> reference = new WeakReference<>(bigObject,queue);

bigObject是一个强引用,导致new BigObejct()的内存空间不会被GC回收。打印如下:

before gc,reference get is ...
queue is null
after gc,reference get is ...
queue is null

实现思路

LeakCanary中对内存泄漏检测的核心原理就是基于WeakReference和ReferenceQueue实现的。

  1. 当一个Activity需要被回收时,就将其包装到一个WeakReference中,并且在ReferenceQueue的构造器中传入自定义的ReferenceQueue。
  2. 然后给包装后的WeakReference做一个标记Key,并且在一个强引用Set中添加相应的Key记录。
  3. 最后主动触发GC,遍历自定义ReferenceQueue中所有的记录,并根据获取的Reference对象将Set中的记录也删除。

经过上面3步之后,还保留在Set中的就是:应当被GC回收,但是实际还保留在内存中的对象,也就是发生泄漏的对象

源码分析

在上面原理介绍的例子中,我们知道一个可回收对象在System.gc()之后就应该被GC回收。可是在Android APP中,我们并不清楚何时系统会回收Activity。但是,按照正常流程,当Activity调用onDestroy()时就说明这个Activity就已经处理无用状态了。因此我们需要监听到每一个Activity的onDestroy()方法的调用

ActivityRefWatch

LeakCanary中监听Activity生命周期是由ActivityRefWatch来负责的,主要是通过注册Android系统提供的ActivitLifecycleCallbacks,来监听Activity的声明周期方法。

application.registerActivityLifecycleCallbacks(lifecycleCallbacks);

当监听到Activity的onDestroy()方法后,会将其传给RefWatcher的watch方法

RefWatcher

它是LeakCanary的一个核心类,用来检测一个对象是否会发生内存泄漏。主要实现是在watch()方法中

String key = UUID.randowUUID.toString();
retainedKeys.add(key);

final KeyedWeakReference reference = new KeyedWeakReference(watchedReference,key,referenceName,queue);

ensureGoneAsync(watchStartNamoTime,reference);
  1. 生成一个随机的字符串key,这个key就是用来表示WeakReference的,就相当于给WeakReference打了一个标签
  2. 第二部分,将被检测对象包装到一个WeakReference中,并将其标识为步骤1中生成key
  3. 调用ensureGoneAsync开始执行检测操作

具体就在ensureGoneAsync中实现内存泄漏的检测

  1. 首先会遍历ReferenceQueue中所有的元素,并根据每个元素中的key,相应的将集合retainedKeys中的元素删除
  2. 判断集合retainedKeys是否还包含被检测对象的弱引用,如果包含说明被检测对象并没有被回收,也就是发生了内存泄漏
  3. 生成Heap“堆”信息,并生成内存泄漏的分析报告,上报给开发人员

内存泄漏的检测时机

内存泄漏的检测与分析是比较消耗性能的,因此为了尽量不影响UI线程的渲染,LeakCanary也做了些优化操作。在ensureGoneAsync中调用了WatchExecutor的execute方法来执行检测操作

实际上是向主线程MessageQueue中插入了一个idleHandler,idleHandler只会在主线程空闲时才会被Looper从队列中取出,并执行。因此能够有效避免内存泄露检测工作占用UI渲染时间。

通过idleHandler也经常做APP启动优化,比如在Application的onCreate()方法中经常做3方库的初始化工作。可以将优先级较低,暂时使用不到的3方库的初始化操作放到idleHandler中,从而加快Application的启动过程。不过个人感觉方法名应该叫addidleMessage更合适,因为向MessageQueue插入的都是Message对象

LeakCanary如何检测其他类

LeakCanary默认只能检测Activity的泄漏,但是RefWatcher的watch()方法传入的参数实际是Object,所以理论上可以检测任何类。LeakCanary的install方法会返回一个RefWatcher对象,我们只需要在Application中保存此RefWatcher对象,然后将需要被检测的对象传给watch方法即可。

Top
Foot