AndroidApp定位跟规避内存泄漏办法研究

AndroidApp定位和规避内存泄漏办法研究

转自:http://www.mysjtu.com/page/M0/S726/726741.html
1. 内容

本文档包含如下内容:

l 如何断定App存在内存泄漏

l 如何定位App的内存泄漏地位

l 如何避免内存泄漏

2. 名词申明

App:Application

VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

RSS - Resident Set Size 实际应用物理内存(包含共享库占用的内存)

PSS - Proportional Set Size 实际应用的物理内存(比例分派共享库占用的内存)

USS - Unique Set Size 过程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

3. Android查看内存的对象

DDMS查看体系内存

在sdk/ android-sdk_eng._linux-x86/tools下,启动ddms,

./ddms

经由过程ddms的sysInfo,如下图,我们可以看到体系内存今朝的分布景象,这是一个饼状图,

从图中看BaiduReader可能占用了12%,10M阁下的内存。

AndroidApp定位跟规避内存泄漏办法研究

应用procrank查看过程内存

procrank 号令可以获合适前体系中各过程的内存应用快照,这里有PSS,USS,VSS,RSS。我们一般调查Uss来反应一个Process的内存应用景象,Uss 的大小代表了只属于本过程正在应用的内存大小,这些内存在此Process被杀掉之后,会被完全的收受接管掉,

Vss和Rss对查看某一Process自身内存状况没有什么价值,因为他们包含了共享库的内存应用,而往往共享库的资料占用比重是很大的,如许就稀释了对Process自身创建内存波动。

而Pss是遵守比例将共享内存分别,某一Process对共享内存的占用景象。

procrank 的代码在 /system/extras/procrank,,在模仿器或者设备上的运行文件位/system/xbin

在adb shell之后,我们运行procrank下图是Help

AndroidApp定位跟规避内存泄漏办法研究

下图是BaiduReader运行下的所有过程的内存应用列表

AndroidApp定位跟规避内存泄漏办法研究从上图我们可以看到,所有的后台守护过程都比基于dalvik的虚拟机过程要小的多,zygote是虚拟机收个过程,由它来负责folk生成其他的虚拟机过程,而刚才PSS中谈到的共享库,其实就是由Zygote加载的,而其他虚拟机过程与Zygote共享这些内存。

应用脚本共同procrank跟踪内存变更

应用procrank来跟踪某过程的应用哪个景象我们经常借助与脚本。如许就可以查看某一段时候的内存变更。

如创建一个文件:trackmem.sh    chmod 775 trackmem.sh

内容如下:

#!/bin/bash

while true; do

adb shell procrank | grep "com.baidu.BaiduReader"

sleep 1

done

运行该脚本:

./trackmem.sh

这个脚本的用处是每1秒钟让体系输出一次BaiduReader的内存应用状况,如下图:

AndroidApp定位跟规避内存泄漏办法研究

AndroidApp定位跟规避内存泄漏办法研究

调查USS的变更,从7M多进步到了9M多,这是因为打开了一个斗劲消费资料的浏览界面,之后的操纵时,络续的反复打开封闭这个界面(Activity),会发明内存只会无意的降落一点,而不会跟从GC的收受接管策略,当Acitivity被封闭之后,相干的资料会一并收受接管,所以我们断定这个Activity很可能存在内存泄漏。

如何断定是否存在内存泄漏

AndroidApp是基于虚拟机的,其内存经管都是由Dalvik代为经管的,GC的收受接管不是及时的,比如一个Activity被Finish掉之后,其内存的引用对象会鄙人次GC收受接管的时辰,经由过程收受接管算法策画,若是这项目组内存已经属于可收受接管的对象,那么这些垃圾对象会被一并收受接管,所以内存的趋势图可能如下:

AndroidApp定位跟规避内存泄漏办法研究

若是我们思疑某一次操纵或者某个界面存在内存泄漏,一般的查找办法是反复这个操纵,或者反复打开封闭这个界面,理论上,每次封闭都邑对应一次大的内存开释,而若是存在内存泄漏的景象,举例如下图,在反复打开封闭Reader的浏览界面的时辰,内存一向在向上爬升,也就是说每次封闭这个Activity的时辰,有些应当开释的内存没有被开释掉

AndroidApp定位跟规避内存泄漏办法研究

如何定位内存泄漏的地位

查找内存泄漏一种斗劲土但斗劲彻底的办法就是代码走查,我们可以一行行的解析对象的创建去留等等,但会很耗时候,也斗劲苍茫

这里给出一种经由过程对象来查找的办法,但此办法只实用于Java层的查找,C/C++是没用的,也就是说只针对与被虚拟机来经管的过程和内存。

如今向大师引荐Eclipse Memory Analyzer tool(MAT),,可以直接应用RCP版本或者安装其eclipse的插件,下载地址是http://www.eclipse.org/mat/downloads.php 。

Mat的解析文件是hprof文件。 这个文件存放了某Process的内存快照

如何从手机或者模仿器获得hprof文件呢?

adb shell

#ps  (找一下要Kill的过程号)

# chmod 777 /data/misc

# kill -10 过程号

AndroidApp定位跟规避内存泄漏办法研究

如许会在/data/misc目次下生成一个带当前时候的hprof文件,比如

heap-dump-tm1291023618-pid1059.hprof

然则这个文件不克不及直接被mat读取,我们须要借助android供给的对象hprof-conv 来把上方的hprof转化为mat可以读取的格局。

起首将文件pull到当前目次

adb pull /data/misc/heap-dump-tm1291023618-pid1059.hprof ./

然后借助hprof-conv转换一下格局,此对象在sdk/android-sdk_eng._linux-x86/tools下面.

./hprof-conv heap-dump-tm1291023618-pid1059.hprof readershot.hprof

用mat或eclipse打开(若是装mat插件的话) ,选择[Leak Suspects Report],如图 :

AndroidApp定位跟规避内存泄漏办法研究如许就Mat就会为我们主动生成一个泄漏揣摩呈报,如下图,

从呈报中呈报的三个题目,我们大约可以断定这些处所存在一些题目,

AndroidApp定位跟规避内存泄漏办法研究AndroidApp定位跟规避内存泄漏办法研究

从上图中Suspect1中,可以看到由class loader加载的HashMap有内存凑集,可能分派了1.6M的内存,所以对比代码中的HashMapEntry,就可以准断定位到有可能存在内存泄漏的处所,经由过程逻辑断定这项目组是否有优化的可能。

这里趁便介绍一下dalvik.system.PahtClassLoader,这个是Android中Dalvik的体系类和法度类的装载器,所有的.dex都须要经由过程它的装载之后生成我们所须要的对象。

AndroidApp定位跟规避内存泄漏办法研究

AndroidApp定位跟规避内存泄漏办法研究别的Mat还供给了其他的视图,比如上图可以经由过程类名/Class loadeer来显现各类所占用的堆空间大小,所占内存的比例,对象的数量,经由过程这些参数我们也可以断定哪些对象可能是不太正常的。

简单介绍一下ShallowHeap和RetainedHeap。

Shallow size就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。在32位体系上,对象头占用8字节,int占用4字节,不管成员变量(对象或数组)是否引用了其他对象(实例)或者赋值为null它始终占用4字节。

Retained size是该对象本身的shallow size,加上从该对象能直接或间接接见到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能收受接管到内存的总和。

借助于Mat堆内存快照的解析,我们根蒂根基可以定位Java层的内存泄漏的题目,Mat是个很强悍的对象,更多的用法请参考http://dev.eclipse.org/blogs/memoryanalyzer/。

而还有一些内存泄漏经由过程Mat是查不出来的,比如native的代码,对C/C++是力所不及的,对于这些题目是本文无法涵盖的,相干可以参考valgrind(http://valgrind.org/)

如何避免内存泄漏

AndroidSDK中有一篇文章专门写了如何避免内存泄漏,这篇文章的中文翻译我贴在了下面。除了下文中提到的Context和View的强引用,还有一些须要重视点:

1:BraodcastReceiver,ContentObserver,FileObserver在Activity onDeatory或者某类声明周期停止之后必然要unregister掉,不然这个Activity/类会被system强引用,不会被内存收受接管。

2:不要直接对Activity进行直接引用作为成员变量,若是不得不这么做,请用private WeakReference<Activity> mActivity来做,雷同的,对于Service等其他有本身声明周期的对象来说,直接引用都须要谨慎推敲是否会存在内存泄漏的可能;

3:很多内存泄漏是因为轮回引用造成的,比如a中包含了b,b包含了c,c又包含a,如许只要一个对象存在其他必然会一向常驻内存,这要从逻辑上来解析是否须要如许的设计。

下文来自http://androidappdocs.appspot.com/resources/articles/avoiding-memory-leaks.html

Avoiding Memory Leaks

避免内存泄漏

Android应用法度,至少是在 T-Mobile G1上,是被分派了16M的Heap。对于手机来说,这已经是很多内存了,然则对开辟者而言,却显的很少。尽管你没有筹算用光所有的内存,也应当尽量罕用内存以至于其他应用法度不被杀掉。越多的应用法度被Android保存在内存里,用户在切换法度的时辰就越快。作为我工作的一项目组,我碰到的大项目组 Android应用法度中的内存泄漏题目都是因为雷同的原因:对 Context对峙一个长生命周期的引用。

在Android里,一个Context被用于很多操纵,然则大项目组是用于加载和接见资料。这就是为什么所有的widget在他们的机关里都接管一个Context的参数。在一个典范的Android应用法度里,你经常用到两种Context,Activity 和Application。开辟者经常把前者传到须要Context的类和办法里。

@Override

protected void onCreate(Bundle state) {

&#160; super.onCreate(state);

&#160; TextView label = new TextView(this);

&#160; label.setText("Leaks are bad");

&#160; setContentView(label);

}

这就意味views有一个对这个activity的引用,也就是对峙了该Activity里的所有引用,经常是全部view体系和它所有的资料。是以若是你"泄漏"了Context("泄漏"意思是你保存了一个引用,是以阻拦了GC收集它),你就泄漏了很多内存。若是你不重视的话,泄漏全部Activity真的很轻易。

当屏幕的orientation变更时,默认景象下体系会烧毁当前的activity再创建一个保存本来状况的新activity,这时Android会从资料中从头加载这个application的UI。如今假设你写的一个application里有一个很大的bitmap,你又不想每次转屏都从头加载。最简单的体式格式就是把它保存为一个static变量:

private static Drawable sBackground;

@Override

protected void onCreate(Bundle state) {

&#160; super.onCreate(state);

&#160; TextView label = new TextView(this);

&#160; label.setText("Leaks are bad");

&#160; if (sBackground == null) {

&#160;&#160;&#160; sBackground = getDrawable(R.drawable.large_bitmap);

&#160; }

&#160; label.setBackgroundDrawable(sBackground);

&#160; setContentView(label);

}

这个代码很是快,然则也是很是错误的,它泄漏了屏幕扭转前的activity。当一个 Drawable附到一个view上时,view就被作为一个callback设置到drawable上。在上方一小断代码里,就意味着该drawable有一个对textview的引用,而这个textview又有对这个activity(就是这个context)的引用,而这个activity里有很多对其他对象的引用(取决你的代码)。

这个例子是一个泄漏Context的最简单的景象,你可以在 Home screen""s source code(办法unbindDrawables())看到当一个activity被烧毁时我们是怎么工作的,我们会设置保存drawable的callback为null。有很多景象可以造成一系列context泄漏,它们会很快地耗光你的内存,这些很是不好。

有两个简单的办法来避免context相干的内存泄漏。最明显的办法是避免context跨越本身的应用局限。上方的例子注解对外部静态变量的引用同样危险。第二种解决办法是用Application context。这个context会存活在全部application生命周期中,它不依附activity的生命周期。若是你想保存一个须要context的长生命周期的对象,记住应用Application context。你可以经由过程调用 Context.getApplicationContext() 或者Activity.getApplication()来获得它。

总之,为了避免context相干的内存泄漏,记得下面的步调:

不要在context-activity里保存长生命周期的引用 (对于activity的引用,应当有和这个activiy雷同的生命周期)

试着应用Application context来庖代context-activity

若是你不想把握非静态内部类的生命周期,就要避免在一个activity里应用它,而要用一个静态的内部类,对外部的这个activity有一个弱引用。这种解决办法有一个实例: ViewRoot和它的内部类中有一个对外部类的WeakReference。

GC对内存泄漏是力所不及的。

参考材料

How to avoid memory leak

How to use Eclipse Memory Analyzer to analyze JVM Memeory

valgrind

MAT Wiki

Understanding Weak References译文

Java HotSpot VM Options

Shallow and retained sizes

JVM Memory Structure

http://developer.android.com/search.html#q= procrank&t=0