热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

android学习笔记(五)调试相关5.6AndroidApp定位和规避内存泄露方法研究

网上看到一位仁兄的这篇文章,写的很不错,摘抄下来备不时之需。1.内容本文档包含如下内容:l如何确定App存在内存泄露l如何定位App的内存泄露位置l怎样避免内存泄露2.
网上看到一位仁兄的这篇文章,写的很不错,摘抄下来备不时之需。
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左右的内存。

wps_clip_image-27750

使用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

wps_clip_image-6790

下图是BaiduReader运行下的所有进程的内存使用列表

wps_clip_image-25484从上图我们可以看到,所有的后台守护进程都比基于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的内存使用状况,如下图:

wps_clip_image-11128

wps_clip_image-21287

观察USS的变化,从7M多提高到了9M多,这是由于打开了一个比较消耗资源的阅读界面,之后的操作时,不断的重复打开关闭这个界面(Activity),会发现内存只会偶尔的下降一点,而不会跟随GC的回收策略,当Acitivity被关闭之后,相关的资源会一并回收,所以我们判断这个Activity很可能存在内存泄露。

怎样判断是否存在内存泄露

AndroidApp是基于虚拟机的,其内存管理都是由Dalvik代为管理的,GC的回收不是及时的,比如一个Activity被Finish掉之后,其内存的引用对象会在下次GC回收的时候,通过回收算法计算,如果这部分内存已经属于可回收的对象,那么这些垃圾对象会被一并回收,所以内存的趋势图大概如下:

wps_clip_image-25715

如果我们怀疑某一次操作或者某个界面存在内存泄露,一般的查找方法是重复这个操作,或者重复打开关闭这个界面,理论上,每次关闭都会对应一次大的内存释放,而如果存在内存泄露的情况,举例如下图,在重复打开关闭Reader的阅读界面的时候,内存一直在向上爬升,也就是说每次关闭这个Activity的时候,有些应该释放的内存没有被释放掉

wps_clip_image-17265

如何定位内存泄露的位置

查找内存泄露一种比较土但比较彻底的方法就是代码走查,我们可以一行行的分析对象的创建去留等等,但会很耗时间,也比较迷茫

这里给出一种通过工具来查找的方法,但此方法只适用于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 进程号

wps_clip_image-10756

这样会在/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],如图 :

wps_clip_image-30488这样就Mat就会为我们自动生成一个泄露推测报告,如下图,

从报告中报告的三个问题,我们大约可以断定这些地方存在一些问题,

wps_clip_image-11048wps_clip_image-9215

从上图中Suspect1中,可以看到由class loader加载的HashMap有内存聚集,大概分配了1.6M的内存,所以对照代码中的HashMapEntry,就可以准确定位到有可能存在内存泄露的地方,通过逻辑判断这部分是否有优化的可能。

这里顺便介绍一下dalvik.system.PahtClassLoader,这个是Android中Dalvik的系统类和程序类的装载器,所有的.dex都需要通过它的装载之后生成我们所需要的对象。

wps_clip_image-1423

wps_clip_image-6928另外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 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) {

  super.onCreate(state);

  TextView label = new TextView(this);

  label.setText("Leaks are bad");

  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) {

  super.onCreate(state);

  TextView label = new TextView(this);

  label.setText("Leaks are bad");

  if (sBackground == null) {

    sBackground = getDrawable(R.drawable.large_bitmap);

  }

  label.setBackgroundDrawable(sBackground);

  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


推荐阅读
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • Python脚本编写创建输出数据库并添加模型和场数据的方法
    本文介绍了使用Python脚本编写创建输出数据库并添加模型数据和场数据的方法。首先导入相应模块,然后创建输出数据库并添加材料属性、截面、部件实例、分析步和帧、节点和单元等对象。接着向输出数据库中添加场数据和历程数据,本例中只添加了节点位移。最后保存数据库文件并关闭文件。文章还提供了部分代码和Abaqus操作步骤。另外,作者还建立了关于Abaqus的学习交流群,欢迎加入并提问。 ... [详细]
  • LINUX学习之centos7营救模式
    今天卸载软件的时候,不小心把GNOME的一些组件给卸了,导致桌面无法正常开启,会卡在启动过程中,而我的开机启动模式又是设置为图形界面,所以一开LINUX就卡住了,进入不了命令行界面 ... [详细]
  • 目录1、将mysql数据导出到SQL文件中(数据库存在的情况)2、将现有的sql文件数据导入到数据库中(前提数据库存在) 3、利用Navicat导出SQL文件和导入SQL文件1)从 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • 点击上方“新机器视觉”,选择加”星标”或“置顶”重磅干货,第一时间送达很早就想总结一下前段时间学习HALCON的心得,但由于其他的事情总是抽不出时间。去年有过一段时间的集中学习,做 ... [详细]
author-avatar
mobiledu2502914875
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有