美文网首页
[性能优化]使用LeakCanary优化你的app

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

作者: qiHuang112 | 来源:发表于2020-01-20 13:15 被阅读0次

    前言

    在日常开发中,可能经常会遇到一些莫名奇妙的崩溃问题,但是仔细查看代码逻辑却似乎也找不出代码中有哪些不对的逻辑。这时候就需要仔细分析,你的代码中是否存在内存泄漏的问题。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

          本文链接:https://www.haomeiwen.com/subject/evmoactx.html