性能优化-内存泄漏

你。 提交于 2019-12-21 00:41:18

由于Java的特有属性,其垃圾回收机制的垃圾回收的时间不确定性,造成了Android的内存泄露问题,本文主要是说明一些Android中的内存泄露问题

内存泄漏概念

  • 在C/C++中,堆内存的开辟和销毁是通过程序员通过malloc/freenew/delete去完成的,而在Java中,程序员只用开辟内存,而不用关心内存怎么去释放,这一切都交由Java的GC去完成

  • 而内存泄漏问题,也就出在GC这里,如果一个对象已经不再被使用,这时候按理说应该回收其内存,但在这时候如果有其他使用的对象正在引用这个对象,那么GC就不能对其回收,从而继续保留在内存中,这样便产生了内存泄漏,究其本质,内存泄露的原因就是内存不在GC的掌控范围之内了

内存分区

内存可以人为的划分为几个区

  • 静态区:存放静态数据和常量
  • 方法区:存放代码
  • 栈区:存放局部变量和基本数据类型,不会产生碎片,效率高,内存小
  • 堆区:基本通过new关键字生成的对象都位于堆区,栈中只保留其索引位置

lrucache算法

按照使用频率决定回收顺序

在查阅ListView源代码的时候,发现其开辟的内存空间在UI层不可见的时候并没有被回收,而是被复用了,也就是ConvertView

当需要加载大量数据或者图片的时候,往往是很耗费内存的,处理不当的话很容易直接造成OOM

那么,在内存和IO之间就需要一个平衡,也就是时间和空间的平衡性

引用分类

另外,为了更加利于GC回收,可以使用Java的一些特殊类进行标识:StrongReferenceSoftReferenceWeakReferencePhatomReference

  • StrongReference:强引用
    • 回收时机:从不回收
    • 使用:对象的一般保存
    • 生命周期:JVM停止的时候才会终止
  • SoftReference:软引用
    • 回收时机:内存不足时
    • 使用:SoftReference<T>结合ReferenceQueue构造,有效期短
    • 生命周期:内存不足为止
  • WeakRefence:弱引用
    • 回收时机:垃圾回收时
    • 使用:SoftReference<T>结合ReferenceQueue构造,有效期短
    • 生命周期:GC回收前
  • PhatomRefence:虚引用
    • 回收时机:垃圾回收时
    • 使用:和ReferenceQueue来跟踪对象呗垃圾回收期回收的活动
    • 生命周期:GC回收前

一些占内存且生命周期长的对象,尽量使用软引用和弱引用

内存回收

垃圾清理的基本概念有

  • 第一,找到未来无法存取的数据,例如所有不受指令操控的内存
  • 第二,回收被利用过的资源

在GC运行的时候会占用内存,一样会降低系统性能,GC回收时间过长导致卡顿

单例模式的内存泄漏

常见的内存泄漏之一就是单例模式的使用,当活动销毁的时候,单例模式的内存并没有被销毁,此时就存在内存泄漏了

private static CtxUtil instance;
    private Context context;
    private CtxUtil(Context context){
        this.context = context;
    }

    public static CtxUtil getInstance(Context context){
        if(instance == null){
            instance = new CtxUtil(context);
        }
        return instance;
    }

如果在调用的时候传递的是当前对象,那么很容易造成调用者的内存泄漏

CtxUtil ctxUtil = CtxUtil.getInstance(this);

但如果传递getApplicationContext(),那么就可以解决这个问题,简单来说就是不要然类持有另一个类的实例就可以了

CtxUtil ctxUtil = CtxUtil.getInstance(getApplicationContext());

其原理就是当前对象和application的上下文生命周期并不相同
结论:能用Applicationcontext就用Application的,单例模式产生的Context周期与app周期相同

Android Studio检查内存泄漏

Android Studio中有一个Monitor工具,可以用来检测内存泄漏
主要涉及MemoryCPUGPUNetwork的监视
选择要监视的应用,查看其各项数据的使用情况,这里主要是Memory的监视

  • 选择Initiate GC手动释放内存,这时候应用会按照自己的需求再分配内存
    如果对app进行操作,例如屏幕旋转,那么获得会销毁重新创建,此时如果Allocated数值不断上升,那么就说明存在内存泄漏问题
  • 选择Dump Java Heap,会生成堆内存快照,可以查看堆内存的使用情况
    多个快照进行对比,可以得到哪些地方内存发生内存变化,也就可以分析出内存泄漏存在的地方
    Reference Tree中可以查看引用情况

检查内存泄漏的一般方法

确定是否存在内存泄露

  • Android Monitors的内存分析
    最直观的看内存增长情况,知道该动作是否发生内存泄露
    假如动作发生之前:GC完后内存1.4M; 动作发生之后:GC完后内存1.6M,那么就有理由怀疑发生了内存泄漏
  • 使用MAT内存分析工具
    MAT分析heap的总内存占用大小来初步判断是否存在泄露
    Heap视图中有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象
    data object一行中有一列是Total Size,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏
    我们反复执行某一个操作并同时执行GC排除可以回收掉的内存,注意观察data objectTotal Size值,正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况
    反之如果代码中存在没有释放对象引用的情况,随着操作次数的增多Total Size的值会越来越大

那么这里就已经初步判断这个操作导致了内存泄露的情况

先找怀疑对象(哪些对象属于泄露的)

MAT对比操作前后的hprof来定位内存泄露是泄露了什么数据对象(这样做可以排除一些对象,不用后面去查看所有被引用的对象是否是嫌疑)
快速定位到操作前后所持有的对象哪些是增加了(GC后还是比之前多出来的对象就可能是泄露对象嫌疑犯)
技巧:Histogram中还可以对对象进行Group,比如选择Group By Package更方便查看自己Package中的对象信息

MAT分析hprof来定位内存泄露的原因所在(哪个对象持有了上面怀疑出来的发生泄露的对象)

  • Dump出内存泄露当时的内存镜像hprof,分析怀疑泄露的类;
  • 把上面2得出的这些嫌疑犯一个一个排查个遍。步骤:
    • 进入Histogram,过滤出某一个嫌疑对象类
    • 然后分析持有此类对象引用的外部对象(在该类上面点击右键List Objects--->with incoming references
    • 再过滤掉一些弱引用、软引用、虚引用,因为它们迟早可以被GC干掉不属于内存泄露(在类上面点击右键Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
    • 逐个分析每个对象的GC路径是否正常,此时就要进入代码分析此时这个对象的引用持有是否合理,这就要考经验和体力了

没有内存泄漏的标准

  • 当app退出的时候,这个进程里面所有的对象应该就都被回收了,尤其是很容易被泄露的(ViewActivity)是否还内存当中,可以让app退出以后,查看系统该进程里面的所有的ViewActivity对象是否为0,在退出以后多GC几次
  • 工具:使用AndroidStudio -> AndroidMonitor -> System Information -> Memory Usage查看Objects里面的viewsActivity的数量是否为0
  • 这样就可以知道ViewActivity是否发生了内存泄漏
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!