【Android论文栏目提醒】:网学会员,鉴于大家对Android论文十分关注,论文会员在此为大家搜集整理了“AndroidApp定位和规避内存泄露方法研究 - 网络与通信”一文,供大家参考学习!
Android App定位和规避内存泄露方法研究 文档类型 调研文档 代码版本
Android 2.2 文档作者 郭振 提交时间 2010/11/22 No 修改后 版本号 修改内容简介 修改日期 修改人 1 V1.0 全文 2010/11/22 郭振 1. 内容 本文档包含如下内容: 如何确定App存在内存泄露 如何定位App的内存泄露位置 怎样避免内存泄露 2. 名词解释 AppApplication VSS - Virtual Set Size 虚拟耗用内存包含共享库占用的内存 RSS - Resident Set Size 实际使用物理内存包含共享库占用的内存 PSS - Proportional Set Size 实际使用的物理内存比例分配共享库占用的内存 USS - Unique Set Size 进程独自占用的物理内存不包含共享库占用的内存 3.
Android查看内存的工具 DDMS查看系统内存 在sdk/
android-sdk_eng._linux-x86/tools下启动ddms ./ddms 通过ddms的sysInfo如下图我们可以看到系统内存目前的分布情况这是一个饼状图 从图中看BaiduReader大概占用了1210M左右的内存。
使用procrank查看进程内存 procrank 命令可以获得当前系统中各进程的内存使用快照这里有PSSUSSVSSRSS。
我们一般观察Uss来反映一个Process的内存使用情况Uss 的大小代表了只属于本进程正在使用的内存大小这些内存在此Process被杀掉之后会被完整的回收掉 Vss和Rss对查看某一Process自身内存状况没有什么价值因为他们包含了共享库的内存使用而往往共享库的资源占用比重是很大的这样就稀释了对Process自身创建内存波动。
而Pss是按照比例将共享内存分割某一Process对共享内存的占用情况。
procrank 的代码在 /system/extras/procrank在模拟器或者设备上的运行文件位/system/xbin 在adb shell之后我们运行procrank下图是Help 下图是BaiduReader运行下的所有进程的内存使用列表 从上图我们可以看到所有的后台守护进程都比基于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的内存使用状况如下图 观察USS的变化从7M多提高到了9M多这是由于打开了一个比较消耗资源的阅读界面之后的操作时不断的重复打开关闭这个界面Activity会发现内存只会偶尔的下降一点而不会跟随GC的回收策略当Acitivity被关闭之后相关的资源会一并回收所以我们判断这个Activity很可能存在内存泄露。
怎样判断是否存在内存泄露 AndroidApp是基于虚拟机的其内存管理都是由Dalvik代为管理的GC的回收不是及时的比如一个Activity被Finish掉之后其内存的引用对象会在下次GC回收的时候通过回收算法计算如果这部分内存已经属于可回收的对象那么这些垃圾对象会被一并回收所以内存的趋势图大概如下 如果我们怀疑某一次操作或者某个界面存在内存泄露一般的查找方法是重复这个操作或者重复打开关闭这个界面理论上每次关闭都会对应一次大的内存释放而如果存在内存泄露的情况举例如下图在重复打开关闭Reader的阅读界面的时候内存一直在向上爬升也就是说每次关闭这个Activity的时候有些应该释放的内存没有被释放掉 如何定位内存泄露的位置 查找内存泄露一种比较土但比较彻底的方法就是代码走查我们可以一行行的分析对象的创建去留等等但会很耗时间也比较迷茫 这里给出一种通过工具来查找的方法但此方法只适用于Java层的查找C/C是没用的也就是说只针对与被虚拟机来管理的进程和内存。
现在向大家引荐Eclipse Memory Analyzer toolMAT可以直接使用RCP版本或者安装其eclipse的插件下载地址是http://www.eclipse.org/mat/downloads.php 。
Mat的解析文件是hprof文件。
这个文件存放了某Process的内存快照 如何从手机或者模拟器获得hprof文件呢 adb shell ps 找一下要Kill的进程号 chmod 777 /data/misc kill -10 进程号 这样会在/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如图 这样就Mat就会为我们自动生成一个泄露推测报告如下图 从报告中报告的三个问题我们大约可以断定这些地方存在一些问题 从上图中Suspect1中可以看到由class loader加载的HashMap有内存聚集大概分配了1.6M的内存所以对照代码中的HashMapEntry就可以准确定位到有可能存在内存泄露的地方通过逻辑判断这部分是否有优化的可能。
这里顺便介绍一下dalvik.system.PahtClassLoader这个是
Android中Dalvik的系统类和程序类的装载器所有的.dex都需要通过它的装载之后生成我们所需要的对象。
另外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是无能为力的对于这些问题是本文无法涵盖的相关可以参考valgrindhttp://valgrind.org/ 如何避免内存泄露 AndroidSDK中有一篇文章专门写了怎样避免内存泄露这篇文章的中文翻译我贴在了下面。
除了下文中提到的Context和View的强引用还有一些需要注意点 1BraodcastReceiverContentObserverFileObserver在Activity onDeatory或者某类声明周期结束之后一定要unregister掉否则这个Activity/类会被system强引用不会被内存回收。
2不要直接对Activity进行直接引用作为成员变量如果不得不这么做请用private WeakReference mActivity来做相同的对于Service等其他有自己声明周期的对象来说直接引用都需要谨慎考虑是否会存在内存泄露的可能 3很多内存泄露是由于循环引用造成的比如a中包含了bb包含了cc又包含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应用程序里你经常用到两种ContextActivity 和Application。
开发者经常把前者传到需要Context的类和方法里。
Override protected void onCreateBundle state super.onCreatestate TextView label new TextViewthis label.setTextLeaks are bad setContentViewlabel 这就意味views有一个对这个activity的引用也就是保持了该Activity里的所有引用经常是整个view体系和它所有的资源。
因此如果你泄露了Context泄露意思是你保存了一个引用因此阻止了GC收集它你就泄露了很多内存。
如果你不注意的话泄露整个Activity真的很容易。
当屏幕的orientation变化时默认情况下系统会销毁当前的activity再创建一个保存原来状态的新activity这时
Android会从资源中重新加载这个application的UI。
现在假设你写的一个application里有一个很大的bitmap你又不想每次转屏都重新加载。
最简单的方式就是把它保存为一个static变量 private static Drawable sBackground Override protected void onCreateBundle state super.onCreatestate TextView label new TextViewthis label.setTextLeaks are bad if sBackground null sBackground getDrawableR.drawable.large_bitmap label.setBackgroundDrawablesBackground setContentViewlabel 这个代码非常快但是也是非常错误的它泄露了屏幕旋转前的activity。
当一个 Drawable附到一个view上时view就被作为一个callback设置到drawable上。
在上面一小断代码里就意味着该drawable有一个对textview的引用而这个textview又有对这个activity就是这个context的引用而这个activity里有很多对其他对象的引用取决你的代码。
这个例子是一个泄露Context的最简单的情况你可以在 Home screens 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