android内存调试工具学习

时间:2021-06-04 10:59:57

一.内存监测工具 DDMS --> Heap

无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方。Android tools中的DDMS就带有一个很不错的内存监测工具Heap(这里我使用eclipse的ADT插件,并以真机为例,在模拟器中的情况类似)。用 Heap监测应用进程使用内存情况的步骤如下:
1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图、Heap视图都是打开的;
2. 将手机通过USB链接至电脑,链接时需要确认手机是处于“USB调试”模式,而不是作为“MassStorage”;
3. 链接成功后,在DDMS的Devices视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息;
4. 点击选中想要监测的进程,比如system_process进程;
5. 点击选中Devices视图界面中最上方一排图标中的“Update Heap”图标;
6. 点击Heap视图中的“Cause GC”按钮;
7. 此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。
 说明:
a) 点击“Cause GC”按钮相当于向虚拟机请求了一次gc操作;
b) 当内存使用信息第一次显示以后,无须再不断的点击“CauseGC”,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化;
c) 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。
  如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值:Heap视图中部有一个Type叫做dataobject,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断:
a) 不断的操作当前应用,同时注意观察data object的Total Size值;
b) 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;
c) 反之如果代码中存在没有释放对象引用的情况,则dataobject的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,直到到达一个上限后导致进程OOM被kill掉。

二.内存分析工具 MAT(Memory Analyzer Tool) 

对于大型软件系统来说,再精细的测试也难以堵住所有的漏洞,即便我们在测试阶段进行了大量卓有成效的工作, 很多问题还是会在 生产环境下暴露出来,并且很难在测试环境中进行重现。 MAT(Eclipse Memory Analyzer)被认为是一个“傻瓜式“的堆转 储文件分析工具, 你只需要轻轻点击一下鼠标就可以生成一个专业的 分析报告。和其他内存泄露分析工具相比, MAT 的使用非常容易, 基本可以实现一键到位,即使是新手也能够很快上手使用。

(1). 工具安装
借助 eclipse + mat 的方式实现 首先到http://18.8.8.81/internal-doc/android_tools/ 下载MemoryAnalyzer-1.1.1.20110824-linux.gtk.x86_64.zip 和其他插件的安装非常类似, MAT 支持两种安装方式, 一种是 “单 机版“的,也就是说用户不必安装 Eclipse IDE 环境,MAT 作为一个 独立的 Eclipse RCP 应用运行; 另一种是” 集成版 “的, 也就是说 MAT 也可以作为 Eclipse IDE 的一部分,和现有的开发平台集成,作为 eclipse 的一个插件使用。

1. 单机版
解压 MemoryAnalyzer-1.1.1.20110824-linux.gtk.x86_64.zip 后直接执行里面的 MemoryAnalyzer 文件即可启动 ,
2. 和 eclipse 集成,作为 eclipse 的一个插件使用

把 MemoryAnalyzer-1.1.1.20110824-linux.gtk.x86_64.zip 解压后,把 pluging 中的所有文件拷贝到 eclipse 中的 pluging 中,然后启动 eclipse

(2). 生成 .hprof 文件
1.启动 eclipse 后,点击 eclipse 右上角的切换
到 DDMS 透视图,并确认 Devices 视图、Heap 视图都是打开的; 2.将手机通过 USB 链接至电脑,链接时需要确认手机是处于“USB 调试”模式,而不是作为“MassStorage” ; 3.链接成功后, DDMS 的Devices 视图中将会显示手机设备的序 在 列号,以及设备中正在运行的部分进程信息。

4.点击选中想要监测的进程,比如 system_process 进程;

5.点击选中 Devices 视图界面中最上方一排图标中的“Dump HPROF file ”图标: 即可在 eclipse 中打开刚刚生成 HPROF 文件。如果不想立刻打开该 hprof 文件,可以选择先把该 hprof 文件保存起来,以后再打开。需要对 eclipse 进行设置,方法: window-Preferences Android DDMS->HPROF Action ,选择 Save todisk,。 进行如上设置后再次点击 生成 hprof 文件前会弹出选择文件保存位置对话框。 这种方式生成的 hprof 文件并不能直接用eclipse 打 开,需要用 hprof-conv 工具进行处理。hprof-conv 工具在 android-sdk-linux_x86/tools 可以找到,方法: hprof-conv old.hprof new.hprof (被处理的文件名字 处理后的文件名字)。当处理完毕,在 eclipse 中 File ->openfile 选择待分析的 hprof 文件即可进行分析。

有些读者可能对这个hprof文件有迷惑,我在这里在引用一些大神的话:

J2SE中提供了一个简单的命令行工具来对java程序的cpu和heap进行 profiling,叫做HPROF。HPROF实际上是JVM中的一个native的库,它会在JVM启动的时候通过命令行参数来动态加载,并成为 JVM进程的一部分。若要在java进程启动的时候使用HPROF,用户可以通过各种命令行参数类型来使用HPROF对java进程的heap或者 (和)cpu进行profiling的功能。HPROF产生的profiling数据可以是二进制的,也可以是文本格式的。这些日志可以用来跟踪和分析 java进程的性能问题和瓶颈,解决内存使用上不优的地方或者程序实现上的不优之处。二进制格式的日志还可以被JVM中的HAT工具来进行浏览和分析,用 以观察java进程的heap中各种类型和数据的情况。
在J2SE 5.0以后的版本中,HPROF已经被并入到一个叫做Java Virtual Machine Tool Interface(JVM TI)中。

(三). 分析HPROF 文件

1. heapdump heap dump 是特定时间点,java 进程的内存快照。有不同的格式来存储这些 数据,总的来说包含了快照被触发时 java 对象和类在 heap 中的情况。由于快照 只是一瞬间的事情,所以 heap dump 中无法包含一个对象在何时、何地(哪个 方法中)被分配这样的信息。

2. 关于 shallowsize、retained size 、 Shallowsize 就是对象本身占用内存的大小,不包含对其他对象的引用,也就 是对象头加成员变量(不是成员变量的值)的总和。在 32 位系统上,对象头占 用 8 字节,int 占用 4 字节,不管成员变量(对象或数组)是否引用了其他对象 (实例)或者赋值为 null 它始终占用 4 字节。故此,对于 String 对象实例来说, 它有三个 int 成员(3*4=12 字节) 、一个 char[]成员(1*4=4 字节)以及一个对象 头 字节) 总共 3*4 +1*4+8=24 字节。(8 , 根据这一原则, String a=” 对 rosen jiang” 来说,实例 a 的 shallow size 也是 24 字节。

Retainedsize 是该对象自己的shallow size,加上从该对象能直接或间接访问 到对象的 shallow size 之和。换句话说,retained size 是该对象被 GC 之后所能回 收到内存的总和。
为何会内存溢出 当 GC 发现通过任何 referencechain(引用链)无法访问某个对象的时候,该对 象即被回收。名词 GC Roots 正是分析这一过程的起点,例如 JVM 自己确保了对 象的可到达性(那么 JVM 就是 GC Roots),所以 GC Roots 就是这样在内存中保持对 象可到达性的,一旦不可到达,即被回收。通常 GC Roots 是一个在 current thread(当前线程)的 call stack(调用栈)上的对象(例如方法参数和局部变量) ,或 者是线程自身或者是 system classloader(系统类加载器)加载的类以及 nativecode(本地代码)保留的活动对象。所以 GC Roots 是分析对象为何还存活于内存中的利器。以下是四种引用类型。
从最强到最弱,不同的引用(可到达性)级别反映了对象的生命周期。

 Strong Ref(强引用):通常我们编写的代码都是 Strong Ref,于此对应的是强可达性,只有去掉强可达,对象才被回收。
Soft Ref(软引用) :对应软可达性,只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有 Strong Ref 时才回收对象。一般可用来实现缓存,通过 java.lang.ref.SoftReference 类实现。
 Weak Ref(弱引用 :比 Soft Ref 更弱,当发现不存在 Strong Ref 时,立刻回收对象而 不 必 等 到 内 存 吃 紧 的 时 候 。 通 过 java.lang.ref.WeakReference 和java.util.WeakHashMap 类实现。
Phantom Ref(虚引用): 虚引用根本不会在内存中保持任何对象本 身 。 一 般 用 于 在 进 入 finalize() 方法 后 进 行 特 殊 的 清 理 过 程 , 通 过 java.lang.ref.PhantomReference 实现。

3.分析hprof 文件

这篇文章下面介绍的很祥细,链接:http://blog.csdn.net/gemmem/article/details/13017999。 

三. 实时内存查看方法
(1). 通过 mat 工具生成 hprof 文件中保存的是在某个时刻的 jvm 内存快照,并
不能实时的分析动态变化的内存,但是 android 提供了相关的 api 可以用来控制 在你需要的时候生成 hprof 文件,并保存在你指定的路径下。方法:android.os.Debug.dumpHprofData("/data/temp/myapp.hprof");例如,可以在捕获某个异常的同时调用上述语句生成.hprof 文件。
2 同时 eclipse 
DDMS

上方中的“内存监测工具 DDMS --> Heap”有介绍。
总结
MAT 只是为你提供一些分析的方向, 通过 MAT 不断的怀疑会产生泄漏的对象, 具体是不是泄漏了,或者为什么泄漏了,必须要自己分析代码,一点点排查。使用 MAT 分析内存查找内存就是找到哪个类的对象的引用没有被释放,找到没有被释放的原因,也就可以很容易定位代码中的哪些片段的逻辑有问题了。