性能优化——内存泄漏之Heap Snapshot和MAT

1,什么是内存泄漏?

比较正常的语言描述我也不懂,按照我的理解就是GC回收不了的那些内存区域就算是内存泄漏,也就是说某些内存中存在的对象不在GC回收的控制的范围内,导致这些内存无法被及时回收掉而长期存在于内存中。这里最好需要了解一下Android GC机制的原理,上网找些资料来看看比较好,譬如Android 操作系统的内存回收机制

图1、GC引用示意图

如上所示,已知Android的内存回收机制是从一个叫GC Roots的对象开始,通过一定的算法去查找所有跟GC Roots是否有直接引用或者间接引用,例如Object A和Object B跟GC Roots是直接引用,那么A和B的内存就不会被回收,虽然Object C和Object D跟GC Roots没有直接的引用关系,但是明显它们跟Object B有直接的关系跟GC Roots是间接的可达关系,so,C和D也不会被回收。再看看Object H,明显它跟GC Roots没有直接可达的关系,那么实际上Object H算是无用对象了,在内存中会等待GC的回收。Object J和Object K虽然跟Object I有着直接的引用关系,但是Object I跟GC Roots没有直接或者间接的可达关系,此时I、J、K的对象所占用的内存也是可以被GC回收掉的。那么如果Object C和Object D已经被使用完了,结束了使命了,以后也不会被使用到,成为无用的对象了,但是由于他们跟GC Roots保持着间接的引用关系,导致C和D占用的内存无法被gc回收掉,使得应用程序可使用的内存变小了,那么此时就可以说C和D引起了内存泄漏。

内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

2,可以作为GC Root 引用点的是:

(1)JavaStack中的引用的对象。
(2)方法区中静态引用指向的对象。
(3)方法区中常量引用指向的对象。
(4)Native方法中JNI引用的对象。
(5)Thread——“活着的”线程。

3,解决内存泄漏的步骤
(1)粗略判断法

首先我们在项目中,肯定是通过肉眼看代码的形式估计是很难看出应用中是否存在内存泄漏的问题的,而且在确认哪里有泄漏的前,我们需要预估一下,应用是否存在内存泄漏的情况,这个步骤相当简单,我一般是通过在设备中打开App,然后跳几个页面,操作几个功能后,就退出App。这时候通过Android Monitor面板上的Monitors上提供的内存分析工具查看,如下图所示:

图2、Android Monitor界面

我们在退出App的情形下,点击System Information --> Memory Usage就会生成内存使用状态的分析日志

图3、生成Memory Usage信息
Applications Memory Usage (kB):
Uptime: 507591 Realtime: 507591

** MEMINFO in pid 2808 [com.cambridge] **
                 Pss  Private  Private  Swapped     Heap     Heap     Heap
               Total    Dirty    Clean    Dirty     Size    Alloc     Free
              ------   ------   ------   ------   ------   ------   ------
Native Heap    16825    16692        0        0    39168    35044     4123
Dalvik Heap    32993    32952        0        0    34874    33035     1839
Dalvik Other      981      980        0        0                           
      Stack      940      940        0        0                           
     Ashmem    10100    10032        0        0                           
  Other dev        4        0        4        0                           
   .so mmap     2049      192      236        0                           
  .apk mmap    26295      116    25752        0                           
  .ttf mmap       47        0        8        0                           
  .dex mmap     8232      228     8004        0                           
  .oat mmap     3205        0      824        0                           
  .art mmap     1900      988      308        0                           
 Other mmap     2503        8     1968        0                           
    Unknown    19662    19660        0        0                           
      TOTAL   125736    82788    37104        0    74042    68079     5962

App Summary
                     Pss(KB)
                      ------
         Java Heap:    34248
       Native Heap:    16692
              Code:    35360
             Stack:      940
          Graphics:        0
     Private Other:    32652
            System:     5844

             TOTAL:   125736      TOTAL SWAP (KB):        0

Objects
             Views:       77         ViewRootImpl:        0
       AppContexts:        2           Activities:        1
            Assets:        3        AssetManagers:        2
     Local Binders:       14        Proxy Binders:       20
     Parcel memory:       10         Parcel count:       42
  Death Recipients:        0      OpenSSL Sockets:        0

SQL
       MEMORY_USED:      215
PAGECACHE_OVERFLOW:       42          MALLOC_SIZE:       62

DATABASES
    pgsz     dbsz   Lookaside(b)          cache  Dbname
       4       24             14         1/23/2  /data/user/0/com.cambridge/databases/xUtils_http_cookie.db
       4       24             85         3/22/4  /data/user/0/com.cambridge/databases/xUtils_http_cookie.db (2)

上面就是App退出后,Memory Usage给出的内存情况,主要看一下Objects一栏,表示该内存中对象的数量,可以看到其中View对象占77个,Activity对象还存在1个,说明系统没有回收掉的对象还存在这么多,有可能是存在着内存泄漏的问题。
粗略的判断可以总结为,使用Android Monitor中的Memory Usage查看Views和Activity的对象是否为0来判断App是否存在内存泄漏的情况,但是这种粗略的判断是无法定位具体哪里发生泄漏的,下面才是查看具体泄漏的情况。
注:adb shell dumpsys meminfo 包名 -d。我们也可以直接使用命名行的方式直接生成Memory Usage的信息。

(2)确定内存泄露的大概范围

在Android Studio中我们可以打开Android Monitor面板,通过一系列的动作的执行来判断大概是哪些地方发生了内存泄漏的问题,具体步骤是这样的,比如我这里先打开我的App的首页,然后等首页稳定后(加载完毕),点击Monitor中的GC,执行几下垃圾回收,让首页的内存状态稳定下来,记录这时的内存使用量

图4、首页内存状态

如上图所示,我们在首页的时候,点击上面的小黄点,执行GC,然后记录下,当前的内存占用,大概是30.95M左右。这时我们跳转到某个页面,再执行一些操作,最后返回到首页,再看一遍内存的状态,如下:

图5、返回到首页后内存状态

上面的图中,1表示刚刚跳转发生时进入第二个页面发生了内存抖动,第二个页面申请了大量的内存,其实是这样的,第二个页面显示的是一张大图。然后我们看2的位置,是返回首页后,我又执行了2遍GC,造成内存回收了一部分。然后看看现在的内存状况,占用了31.86M,跟之前没有跳转时的30.95M相比,在同一个Activity里内存多消耗了近1M多,久可以大致的判断其实在跳转第二个页面时,第二个页面发生了内存泄漏问题,也就是说现在大致可以将内存泄漏的范围确定在第二个Activity内了。

(3)Heap Snapshot进一步确定内存泄漏的位置

通过Heap Snapshot工具进一步查找内存泄漏的位置


图6、Dump Java heap

如上所示,我们可以点击Monitor面试上的Dump Java heap按钮去生成一个.hprof的文件,该文件包含了再这一时间点下的App内存的各种状况,打开.hprof文件如下图所示

图7、Heap Snapshot界面

上图打开后的hprof文件的样子,
A区域:列举了堆内存中所有的类
其中我们先选择Package Tree View,表示查看我们自己app下的类,然后看看图中方框中4个属性的意思:
Total Count:内存中该类的对象个数。
Heap Count:堆内存中该类对象的个数。
Sizeof:物理大小。
Shallow Size:该对象本身占用内存的大小。
Retained Size:释放该对象后,节省的内存大小。

B区域:当我们点击某个类时,右边的B区域会显示该类的实例化对象,这里面会显示有多少个实体,以及详细信息。
Depth:深度。
Shallow Size:对象本身内存大小。
Dominating Size:管辖的内存大小。

C区域:显示哪些对象应用了B区域中的对象。

通过Heap Snapshot工具进一步查找内存泄漏的位置,就是反复操作和生成hprof文件,进行对比,发现哪些对象有异常情况(例如突然多了一个实例),那么就点击该实例,在C区域中查找我们自己代码中的引用,通过查找到的线索去回溯相关代码,找到内存泄漏的地方加以解决。比较麻烦!!!

(4)使用MAT进行查找

上面介绍了Heap Snapshot,看样子通过Heap Snapshot查找内存泄漏的位置是比较麻烦的,所幸Heap Snapshot帮我们生成了.hprof文件了,那么就可以通过更高级的MAT工具打开.hprof文件进行分析了。MAT下载地址

图8、导出.hprof文件

打开MAT,File--Open Heap Dump--选择.hprof文件导入,然后MAT会生成一个内存泄漏的Overview。

图9、MAT生成的Overview

MAT工具最常用的是Histogram


图10、打开Histogram
图11、Histogram

如上所示,Histogram可以显示.hprof文件里涉及的所有的对象,都一一列表在里面,并且其中涉及到多少引用的数量等等。下面我们就要通过一种最简单的方式来使用Histogram来查找内存泄漏的问题。
我们可以在app里针对同一个操作,产生2个.hprof文件,通过Histogram将2个.hprof文件的对象信息进行对比就能很快了解,2次操作中哪些对象变化了。

图12、选择对比的.hprof文件

通过上图的操作,可以选择另外一个.hprof文件进行对比,对比结果如下:

图13、对比后的结果

通过生成的对比后的结果可以看到,其实2个.hprof文件之间的对比就是对比它们涉及到的对象引用的数量的变化,我们可以在上图看到数字的变化,0代表2次操作过程该对象没有变化,+代表增加了引用,-代表减少了引用,尤其是+号代表的对象,要格外注意,有可能就是发生内存泄漏的地方了。
如果我们通过对象找到了可能发生内存泄漏的对象时,这时候回到某一个.hprof文件的Histogram列表里,找到这个对象,然后右键点击

图14、找到内存泄漏的引用

选择List Object,其中“with outgoing references”代表该对象引用了哪些对象,“with incoming references”代表哪些对象引用了该对象。选择“with incoming references”,进入下面的视图


图15、with incoming references

上面的视图列举了发生内存泄漏的对象被哪些对象引用的情况,然后我们右键消除一些软引用弱引用等等情况

图16、消除软引用弱引用等等情况

生成了下面的视图


图17、发生内存泄漏的对象

好了,撸完了,很蛋疼,只学习了Heap Snapshot和MAT的使用步骤,但是博客中的Demo是我真是项目中的,居然没有找到合适的内存泄漏的地方来演示一下,所以上面的例子可以忽略了,只记着heap snapshot和MAT的使用即可啊~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容