Android—内存泄漏及LeakCanary源码解析

内存泄露:程序在向系统申请分配内存空间后(new),在使用完毕后未释放。结果导致一直占据该内存单元,我们和程序都无法再使用该内存单元,直到程序结束,这是内存泄露。

内存溢出(OOM):程序向系统申请的内存空间超出了系统能给的。比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。又比如一车最多能坐5个人,你却非要塞下10个,车就挤爆了。

大量的内存泄露会导致内存溢出(oom)。

引起内存泄漏的情况

1.对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。

  • 比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
  • 资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
  • 对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
  • Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。

2.内部类持有外部类引用,Handler,Runnable匿名内部类导致内存泄露,内部类天然持有外部类的实例引用,activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类,那么就会导致activity无法回收,进而导致activity泄露。​​

  • 在onDestroy方法中调用Handler的removeCallbacksAndMessages方法,清空消息。
  • 静态内部类不持有外部类的引用,使用静态的handler不会导致activity的泄露,还要用WeakReference 包裹外部类的对象。弱引用:private WeakReference<Activity类名> weakReference;  创建Handler时传入activity对象。因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。
  • 将Handler放到抽取出来放入一个单独的顶层类文件中。
  • 不要让生命周期长于Activity的对象持有到Activity的引用。

3.静态内部类持有外部成员变量(或context):可以使用弱引用、使用ApplicationContext。

  • 用 Activity 或Service的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。
  • 用 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
  • 弱引用:private WeakReference<类名> weakReference;  创建静态内部类对象时传入外部类。

4.集合中没用的对象没有及时remove。

  • 我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。
  • 在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

5.WebView

WebView单独存放在一个activity中,onDestory()中调用android.os.Process.killProcess(android.os.Process.myPid())等方法杀死进程。

activity泄漏可以使用LeakCanary。

内存

JAVA是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。

栈(stack):存放基本类型变量数据和对象的引用。(不是对象)。

堆(heap):堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。

常量池:存放字符串常量和基本类型常量(public static final)

方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。

内存泄露原因分析

在JAVA中JVM的栈记录了方法的调用,每个线程拥有一个栈。在线程的运行过程当中,执行到一个新的方法调用,就在栈中增加一个内存单元,即帧(frame)。在frame中,保存有该方法调用的参数、局部变量和返回地址。然而JAVA中的局部变量只能是基本类型变量(int),或者对象的引用。所以在栈中只存放基本类型变量和对象的引用。引用的对象保存在堆中。

当某方法运行结束时,该方法对应的frame将会从栈中删除,frame中所有局部变量和参数所占有的空间也随之释放。线程回到原方法继续执行,当所有的栈都清空的时候,程序也就随之运行结束。

而对于堆内存,堆存放着普通变量。在JAVA中堆内存不会随着方法的结束而清空,所以在方法中定义了局部变量,在方法结束后变量依然存活在堆中。

综上所述,栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收。

当一个对象不需要使用了,但是其他对象一直有该对象的引用会导致该对象无法被回收,造成内存的浪费。这就是造成内存泄露的原因。

垃圾回收机制

垃圾回收(garbage collection,简称GC)可以自动清空堆中不再使用的对象。在JAVA中对象是通过引用使用的。如果再没有引用指向该对象,那么该对象就无从处理或调用该对象,这样的对象称为不可到达(unreachable)。垃圾回收用于释放不可到达的对象所占据的内存。

垃圾回收实现思想

引用计数法

简单理解就是记录一个对象被引用的次数,一个引用被使用引用计数器就+1,反之就-1,当引用次数为0就说明是一个垃圾对象可以被回收了。

可达性分析法

根据是否被GC Root引用确认是否是垃圾对象要被GC回收。

引用类型

从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

1. 强引用(Strong reference)
实际编码中最常见的一种引用类型。常见形式如:A a = new A();等。强引用本身存储在栈内存中,其存储指向对内存中对象的地址。如果一个对象具有强引用,则无论在什么情况下,GC都不会回收被引用的对象。

2. 软引用(Soft Reference)
软引用的一般使用形式如下:

A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);

表示一个对象处在有用但非必须的状态。如果一个对象具有软引用,在内存空间充足时,GC就不会回收该对象;当内存空间不足时,GC会回收该对象的内存(回收发生在OutOfMemoryError之前)。

3. 弱引用(Weak Reference)
同样的,软引用的一般使用形式如下:

A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);

无论当前内存是否紧缺,GC都将回收被弱引用关联的对象。

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被GC回收了,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中,以便在恰当的时候将该弱引用回收。

4. 虚引用(Phantom Reference)

虚引等同于没有引用,这意味着在任何时候都可能被GC回收,设置虚引用的目的是为了被虚引用关联的对象在被垃圾回收器回收时,能够收到一个系统通知。

虚引用在使用时必须和引用队列(ReferenceQueue)联合使用。

ReferenceQueue queue=new ReferenceQueue();
PhantomReference pr=new PhantomReference(object.queue);

内存泄露原因

不再需要的对象依然被引用,导致对象被分配的内存无法被回收

其实在Android中会造成内存泄露的情景无外乎两种:

  • 全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。
  • 活在Activity生命周期之外的线程。没有清空对Activity的强引用。

LeakCanary

由Square开源的一款轻量第三方内存泄漏检测工具,可以实时监测Activity,并提高内存泄漏信息。

原理:watch一个即将要销毁的对象

使用方法:在Applicantion的子类里。

 LeakCanary.install(this);
  public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

内部建立了一个refWatcher对象,通过refWatcher监听activity回收情况。

  • DisplayService是发生内存泄漏时的通知服务

  • excludedRefs()是排除Android源码出现的内存泄漏问题

最主要的是AndroidRefWatcherBuilder.buildAndInstall()

进入buildAndInstall()方法。

  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
// 根据app包名生成LeakCanary关联应用
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
// 监听Activity
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
// 监听Fragment
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

  ActivityRefWatcher.install(context, refWatcher);

  public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

// 通过在Application注册监听每个Activity的生命周期,然后转发给RefWatcher
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };

  FragmentRefWatcher.Helper.install(context, refWatcher);

    public static void install(Context context, RefWatcher refWatcher) {
      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
      .......
      Helper helper = new Helper(fragmentRefWatchers);
      Application application = (Application) context.getApplicationContext();

//给application注册activityLifecycleCallbacks监听
      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }

//在onActivityCreated的时候遍历fragmentRefWatchers队列给每个watcher调用watchFragments(activity)方法
    private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
        new ActivityLifecycleCallbacksAdapter() {
          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            for (FragmentRefWatcher watcher : fragmentRefWatchers) {
              watcher.watchFragments(activity); 
            }
          }
        };

// 到watchFragments(activity)方法中
    private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {

//在Fragment摧毁的时候调用watch方法
        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }

        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };

  @Override public void watchFragments(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
//watchFragments方法里面又注册了一层监听
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
  }

总结:

  • 通过 Application.registerActivityLifecycleCallbacks() 监听Activity的生命周期,在onActivityDestroyed阶段调用RefWatcher的watch(Object)方法。
  • 通过FragmentManager.registerFragmentLifecycleCallbacks() 监听Fragment的生命周期并在onFragmentViewDestroyed和onFragmentDestroyed阶段调用RefWatcher的watch(Object)方法。

我们得知监听最后都是通过调用RefWatcher的watch(Object)方法,接下来到RefWatcher的watch(Object)方法里面看看。

//watchedReference传入的就是activity或fragment对象
  public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
//为监听生成唯一ID
    String key = UUID.randomUUID().toString();
//将key值存入一个队列
    retainedKeys.add(key);

//生成reference对象,封装了watchedReference和key等。
//queue是Reference队列,对象被回收了,那么对应的弱引用对象会在回收时被添加到queue中,
//重点:KeyedWeakReference内部继承了弱引用类,reference是弱引用对象,调用GC时就会被回收。
    final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//最重要的是这个方法,传入了监听对象
    ensureGoneAsync(watchStartNanoTime, reference);
  }

//watchExecutor.execute方法确保了主线程空闲,生命周期走完后才会在子线程运行run方法
  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

接下来看看ensureGone方法的具体实现

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

// 将queue队列中对象的key从key队列中移除,因为在queue队列的reference已经被回收
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }

// 如果key队列没有该reference的key值,说明在某次GC已经回收该对象,没有内存泄漏,不需要处理
    if (gone(reference)) {
      return DONE;
    }

// 执行一次GC
    gcTrigger.runGc();

// 再检查
    removeWeaklyReachableReferences();

// key队列还有key值,说明已经内存泄漏,dump
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

ensureGone()主要将已经GC掉的弱引用对象从队列中移除,然后判断是否已经没有,如果没有跳出,有则会执行GC后再检查,如果有说明内存泄漏,创建dump文件,分析dump文件。

RefWatcher.watch(Object)总结:

RefWatcher.watch(Object)会为每一个监听引用对象(Activity或Fragment)提供唯一ID,RefWatcher类有两个队列分别存放reference对象和key,在主线程走完生命周期后通过 Retryable.run()在子线程检测内存泄漏,监测是ensureGone()方法通过两个队列来判断其是否reference对象是否GC回收。

LeakCanary总结:

LeakCanary调用install方法,内部创建RefWather对象,对象分别设置了对Activity和Fragment在Destroy阶段的监听,就是当Activity和Fragment在Destroy阶段时调用watch方法传入Activity或Fragment对象并生成相对应的唯一值key封装成reference弱引用对象(注意是弱引用对象,因为弱引用对象GC后会被传入Reference队列),最后调用sureGone方法在主线程生命周期走完后,在子线程中进行检查,先将Reference队列的对象的key从key队列中移出,再判断现reference对象的key在不在key队列里,不在则该对象已经被回收,否则再调用一次GC,再判断一次,有内存泄漏便会进行 heap dump 的操作以获取当前内存的 hprof。通过对hprof文件进行解析,计算出GC root的最短引用路径,反馈异常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章