[性能优化]使用LeakCanary优化你的app

前言

在日常开发中,可能经常会遇到一些莫名奇妙的崩溃问题,但是仔细查看代码逻辑却似乎也找不出代码中有哪些不对的逻辑。这时候就需要仔细分析,你的代码中是否存在内存泄漏的问题。LeakCanary是Square公司开源的一款性能优化工具,它能够帮你方便的分析你的app中是否存在内存泄漏的问题。在使用LeakCanary之前,让我们先来了解几个概念。

一些概念

  • Java虚拟机
    相信学过Android的你对Java虚拟机一定不会陌生,但还是简单的介绍一下Java虚拟机。如果你学过C/C++,一些C/C++书中都会强调你需要手动分配内存,并在使用之后手动回收内存。但是好像Java中从来没有人让你释放内存啊,这一切都需要归功于Java虚拟机,Java虚拟机能够帮我们自动释放可回收内存。当然,java虚拟机除了能够帮助我们自动回收内存之外,还有很多别的功能,但本文的重点在于内存泄漏,而安卓使用的虚拟机与Java虚拟机相似。所以下面我们聊一聊Java虚拟机的垃圾回收吧。

  • GC
    GC全称(Garbage Collection),也就是垃圾回收,Java虚拟机主要通过两种途径自动帮我们回收内存。

    • 引用计数
      Java虚拟机会给对象增加一个引用计数器,每当程序引用一次对象,计数器就会加一;反之,每当一个引用计数器失效时,计数器就会减一。当计数器的值为0,则说明此对象没有被引用,可以被回收。
      举个例子
      Object obj = new Object(); // 计数器 + 1 = 1
      obj = null; // 计数器 - 1 = 0,GC回收
      // 但是如果对象相互调用,引用计数器就无法使得GC回收
      Object a = new Object(); // a的引用计数为1 
      Object b = new Object(); // b的引用计数为1 
      a.next = b; // a的引用计数为2 
      b.next = a; // b的引用计数为2 
      a = null; // a的引用计数为1,尽管已经显示地将a赋值为null,但是由于引用计数为1,GC无法回收a
      b = null; // b的引用计数为1,同理,GC也不回收b
      

    为了解决对象之间相互引用导致的无法GC的问题,Java虚拟机还有另一种GC策略。

    • 可达性分析
      设立若干根对象(GC Root) ,每个对象都是一个子节点,当一个对象找不到根节点,也就是无人引用时,标志其不可达。
      可以作为GC Root的对象包括:
      1.jvm栈中引用的对象
      2.方法区中静态变量引用的对象
      3.方法区中常量引用的对象
      4.本地方法栈中引用的对象
      5.新生代,活不了多久就死的对象,比如局部变量,用复制算法[1]
      6.老年代,生命周期长的对象,活的久不过也是会死的,用标记清除算法[2]
      7.永久代[3] -----基本上GC不回收

    但即使Java虚拟机已经如此优秀,它也不能保证所有的可回收内存都能正常回收,

  • 内存泄漏
    内存泄漏是指在app运行的过程中,由于内存并没有合理的回收,如:生命周期长的对象持有了生命周期短的对象的引用,导致生命周期短的对象一直无法回收。当这种情况累积到一定程度,可分配的栈内存不足的时候,就会导致OOM,我们就看到了程序崩溃。而且这种崩溃不像一般的程序崩溃那样能够复现,所以直接由程序崩溃,导致了程序员崩溃。
    例如

    • 在onDestroy()调用Android活动实例的方法后,不再需要该活动实例,并且在静态字段中存储对该活动的引用将防止其被垃圾回收。
    • 添加一个Fragment到backstack而没有在Fragment.onDestroyView()中清除它的view的成员。
    • 在一个对象中以成员的方式保存了一个Activity的context,而Activity在配置更改时依然存在。
    • 注册的绑定生命周期对象的监听、广播接收器、RxJava订阅等,在生命周期结束的时候忘记取消注册。
  • OOM
    OOM是(Out Of Memory)的简称,就是内存不足的意思,类似的问题也有StackOverflow,写一个简单的没有出口的递归函数就能看到。

使用LeakCanary

内存泄漏在安卓app中十分常见,小内存泄漏的积累会导致应用内存不足,并导致OOM崩溃。LeakCanary将帮助我们在开发期间找到这些内存泄漏。
使用LeakCanary十分简单,只需要找到·build.gradle·,并在·dependencies·中加入引用即可。

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.1'
}

我们可以通过过滤LeakCanary标签在Logcat中查看。

分析内存泄漏

当发生内存泄漏的时候,LeakCanary会自动帮你保存内存泄漏信息,并将内存泄漏相关的代码以另一个app的形式展示给你,你可以根据提示,修改代码,从而解决内存泄漏的问题。

如何解决内存泄漏呢

内存泄漏常常存在的原因是因为两个或多个对象生命周期不同,同时存在相互引用。导致生命周期短的对象被生命周期长的对象引用后无法正常回收,从而造成内存泄漏。
下面是一个可能经常遇见的内存泄漏的小例子:

  • Activity内存泄漏
    安卓开发中,我们时常会把Activity当做Context传入某些单例如UserInfo等类中,而我们知道,单例的生命周期可能是整个Application的生命周期,远远要比Activity的生命周期要长。如果使用在单例中某些成员变量保存了Activity的引用,当Activity被关闭的时候,就会导致内存泄漏了。所以,当我们写代码的时候,要格外的慎重,如果Context不是必须传入Activity,我们可以将Context传入Application的Context。如果实在非要传入Activity,你可以在使用完Activity只有,将相关的成员变量置空,这个时候,如果发成GC,Activity的引用计数为0,自然就能正常GC了。

这应该是年前写的最后一篇了,希望这篇文章能够帮到你。


  1. 最初是将内存分为相等的两块,只用其中的一块,当这块内存满的时候,将其中存活的对象复制到另一块内存中,然后GC 回收释放之前这块内存。 ↩

  2. 就是遍历GC Root,标记可达不可达对象,然后回收不可达对象,这种算法缺点是效率低,无法回收连续物理内存,后来升级为标记 - 整理算法,将可达对象移动到内存的一端,然后GC回收剩下部分连续的物理内存。 ↩

  3. 分代算法,在Java中,将内存中的对象按照生命周期长短分成新生代,老年代,永久代 ↩

你可能感兴趣的:([性能优化]使用LeakCanary优化你的app)