说到内存优化,不得不提的就是内存泄露了。但是到底什么是内存泄露呢?按照百度百科的说法就是:

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

每个APP的内存都是有限的,不能毫无顾忌的使用,释放无用的内存就很重要了。而JAVA不像C++之类的语言,不需要手动去delete,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,但gc只能回收无用并且不再被其它对象引用的那些对象所占用的空间。那么我们要做的就是避免无用的对象无法被GC回收。

一、如何判断对象已死

这里通过查阅资料发现有两种算法
1.引用计数算法
2.可达性分析算法
考虑到引用计数算法的缺点,只介绍可达性分析算法:
Android内存优化(二) ——内存泄露检测
可达性分析算法中,通过一系列的gc root为起始点,从一个GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

java中可作为GC Root的对象有:

Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。

Thread - 活着的线程

Stack Local - Java方法的local变量或参数

JNI Local - JNI方法的local变量或参数

JNI Global - 全局JNI引用

Monitor Used - 用于同步的监控对象

Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于”JVM持有”的了。

但是即使在可达性分析算法中不可达的对象,也并非一定要死。当gc第一次扫过这些对象的时候,他们处于“死缓”的阶段。要真正执行死刑,至少需要经过两次标记过程。如果对象经过可达性分析之后发现没有与GC Roots相关联的引用链,那他会被第一次标记,并经历一次筛选。这个对象的finalize方法会被执行。如果对象没有覆盖finalize或者已经被执行过了。虚拟机也不会去执行finalize方法。Finalize是对象逃狱的最后一次机会。

二、引用

提到java的引用,给我的感觉就类似于c的指针了。通俗的讲,通过A能调用并访问到B,那就说明A持有B的引用,或A就是B的引用。JAVA中的引用有Strong reference(强), SoftReference(软), WeakReference(弱), PhatomReference(虚)四种引用类型,不同的引用都与GC过程密切相关。

强引用就是在程序代码中普遍存在的,比如”Object obj = new Object()”这种引用,只要强引用还在,垃圾收集器就不会回收被引用的对象。

软引用用来定义一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要内存溢出之前,会将这些对象列入回收范围进行第二次回收,如果回收后还是内存不足,才会抛出内存溢出。

弱引用也是用来描述非必须对象。但他的强度比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器回收时,无论内存是否足够,都会回收掉被弱引用关联的对象。

虚引用也称为幽灵引用或者幻影引用,是最弱的引用关系。一个对象的虚引用根本不影响其生存时间,也不能通过虚引用获得一个对象实例。虚引用的唯一作用就是这个对象被GC时可以收到一条系统通知。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且生命周期较长的对象时候,可以尽量应用软引用和弱引用技术。

三、内存泄露的排查

(一) 使用Android Profile来查看内存情况

基于Android 3.0的 Android Profile来查看内存情况,资料来自:
https://developer.android.com/studio/preview/features/android-profiler.html

在Android Studio 3.0中,我们点击下图圆圈中的图标运行分析工具
Android内存优化(二) ——内存泄露检测
接着我们会看到这样的界面
Android内存优化(二) ——内存泄露检测
这里主要有网络,内存,cpu占用,以及应用的事件,比如触摸,滑动事件都有显示。接着我们点击蓝色的memory区域
Android内存优化(二) ——内存泄露检测

① 强制执行垃圾收集事件的按钮。
② 捕获堆转储的按钮。
③ 记录内存分配的按钮。
④ 放大时间线的按钮。
⑤ 跳转到实时内存数据的按钮。
⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
⑦ 内存使用时间表,其中包括以下内容:
每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。
虚线表示已分配对象的数量,如右侧y轴所示。
每个垃圾收集事件的图标

与之前的Android监控工具相比,新的内存分析器记录了更多内存使用情况,所以看起来你的内存使用量会更高。内存分析器监视一些额外的类别,这些类别增加了总数。

(二) 使用Android Studio的分析工具

接着刚刚的memory界面。首先我们先在手机应用中进入各个页面并且返回主页,然后我们在memory界面中先进行gc一下,也就是点击一下① 按钮。然后我们在点击②按钮会出现如下界面:
Android内存优化(二) ——内存泄露检测
这里我们就可以查看每个对象的个数,一般如果某个activity已经退出了还在内存中,就应该考虑是否内存泄露了。
接下来我们点击Android内存优化(二) ——内存泄露检测 保存内存快照,后缀为.hprof文件
接下来我们在Android Studio中点击File->Open ,选择刚刚保存的文件。
Android内存优化(二) ——内存泄露检测
点击上图的红色箭头的位置,我们可以看到分析器任务的工具栏
Android内存优化(二) ——内存泄露检测
分析器任务只能检测两块,Activities的泄露和重复的字符串
点击运行进行分析
Android内存优化(二) ——内存泄露检测
这里我们就可以简单查看内存的泄露情况,然后这种方法也并不是特别完善,更方便的工具是Mat。

(三) 使用Mat工具进行分析

Memory Analyzer Tool基于eclipse
可以直接下载:
http://www.eclipse.org/mat/downloads.php

在使用mat之前我们需要把快照文件转换一下,
转换工具在sdk/platform-tools/hprof-conv
-z:排除不是app的内存,比如Zygote
使用命令:
hprof-conv -z src dst
例如:
hprof-conv -z 1.hprof 2.hprof

然后在mat中打开
Android内存优化(二) ——内存泄露检测

然后点击

Android内存优化(二) ——内存泄露检测
接下来我们就可以在红框里搜索想要查看的具体对象

Android内存优化(二) ——内存泄露检测

图表中的
1.shallow head 指的是某一个对象所占内存大小。
2.retained heap:指的是一个对象与所包含对象所占内存的总大小。

这里我还可以列出外部和外部对象引用

Android内存优化(二) ——内存泄露检测

out查看这个对象持有的外部对象引用。
incoming查看这个对象被哪些外部对象引用。

最后我们就查看具体对象为何没有被gc回收,选择排除软弱虚引用

Android内存优化(二) ——内存泄露检测

Android内存优化(二) ——内存泄露检测
这样我们就找到了对象没有被回收的原因,这里是第三方库持有了该activity,具体的需要实际情况分析,总之可以找到最终引用该对象的地方,然后在代码中排查问题。

(四)、使用LeakCanary进行检测

这个库能检测内存泄露,并且通过通知提示,但是我在使用的时候碰到一些bug,总的来说还是可以采集到一些具体情况的,具体的使用方法这里就不细说了,大家感兴趣可以自行查阅资料。

内存泄露的分析就到这里了,下一篇文章会总结内存泄露的常见场景以及解决方法。

相关文章: