Android内存泄露调试

版权声明:本文为博主原创文章,未经博主允许不得转载。

版权声明:本文为兄弟连原创文章,未经博主允许不得转载。

Android 内存泄漏调试 
一、概述 
如果我们编写的代码当中有太多的对内存使用不当的地方,难免会使得我们的设备运行缓慢,甚至是死机。为了能够使得 Android 应用程序安全且快速的运行, Android 的每个应用程序都会使用一个专有的 Dalvik 虚拟机实例来运行,即每个应用程序都是在属于自己的进程中运行的。一方面,如果程序在运行过程中出现了内存泄漏的问题,仅仅会使得自己的 
进程被 kill 掉,而不会影响其他进程(如果是 system_process 等系统进程出问题的话,则会引起系统重启)。另一方面 Android 为不同类型的进程分配了不同的内存使用上限,如果应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被 kill 掉。

Android 为应用进程分配的内存上限如下所示: 
位置: /ANDROID_SOURCE/system/core/rootdir/init.rc 部分脚本

```
# Define the oom_adj values for the classes of processes that can be killed by the kernel.
# These are used in ActivityManagerService.
setprop ro.FOREGROUND_APP_ADJ 0
setprop ro.VISIBLE_APP_ADJ 1
setprop ro.SECONDARY_SERVER_ADJ 2
setprop ro.BACKUP_APP_ADJ 2
setprop ro.HOME_APP_ADJ 4
setprop ro.HIDDEN_APP_MIN_ADJ 7
setprop ro.CONTENT_PROVIDER_ADJ 14
setprop ro.EMPTY_APP_ADJ 15
# Define the memory thresholds at which the above process classes willbe killed.
# These numbers are in pages (4k).
setprop ro.FOREGROUND_APP_MEM 1536
setprop ro.VISIBLE_APP_MEM 2048
setprop ro.SECONDARY_SERVER_MEM 4096
setprop ro.BACKUP_APP_MEM 4096
setprop ro.HOME_APP_MEM 4096
setprop ro.HIDDEN_APP_MEM 5120
setprop ro.CONTENT_PROVIDER_MEM 5632
setprop ro.EMPTY_APP_MEM 6144
# Write value must be consistent with the above properties.
# Note that the driver only supports 6 slots, so we have HOME_APP at the same memory level as
services.
write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15
write /proc/sys/vm/overcommit_memory 1
write /proc/sys/vm/min_free_order_shift 4
write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144
# Set init its forked children‘s oom_adj.
write /proc/1/oom_adj -16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

二、常见的内存使用不当的情况

(一)查询数据库没有关闭游标 
描述: 
程序中经常会进行查询数据库的操作,但是经常会有使用完毕 Cursor 后没有关闭的情况。 
如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况 
下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

示例代码:

Cursor cursor = getContentResolver().query(uri ...);
if (cursor.moveToNext()) {... ...}
  • 1
  • 2
  • 1
  • 2

修正示例代码:

Cursor cursor = null;
try {
cursor = getContentResolver().query(uri ...);
if (cursor != null && cursor.moveToNext()) {... ...}
}
finally {
if (cursor != null) {try {cursor.close();}catch (Exception e) {} }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(二)构造 Adapter 时,没有使用缓存的 convertView 
描述: 
以构造 ListView 的 BaseAdapter 为例,在 BaseAdapter 中提高了方法: 
public View getView(int position, View convertView, ViewGroup parent)来向 ListView 提供每一个 item 所需要的 view 对象。初始时 ListView 会从 BaseAdapter 中根 据当前的屏幕布局实例化一定数量的 view 对 象 ,同 时 ListView 会将这些 view 对象缓存起来 。 
当向上滚动 ListView 时,原先位于最上面的 listitem 的 view 对象会被回收,然后被用来构 造新出现的最下面的 list item 。这个构造过程就是由 getView() 方法完成的, getView()的第二个形参 View convertView 就是被缓存起来的 list item 的 view 对象 ( 初始化时缓存 中没 有 view 对 象则 convertView 是 null) 。 由此 可 以 看出 , 如 果我 们 不 去使 用convertView ,而是每次都在 getView() 中重新实例化 一个 View 对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。 ListView 回收 listitem 的 view 对象的过程可以查看 :android.widget.AbsListView.Java –> void addScrapView(View scrap) 方法。

示例代码:

public View getView(int position, View convertView, ViewGroup parent) {
View view = new Xxx(...); ... ...
return view;
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

修正示例代码:

public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) { view = convertView; populate(view, getItem(position)); ...}
else { view = new Xxx(...); ...}
return view;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(三)Bitmap 对象不在使用时调用 recycle() 释放内存 
描述: 
有时我们会手工的操作 Bitmap 对象,如果一个 Bitmap 对象比较占内存,当它不在被使 用 
的时候,可以调用 Bitmap.recycle() 方法回收此对象的像素所占用的内存,但这不是必须 
的 , 视情况而定。 
可以看一下代码中的注释:

/* Free up the memory associated with this bitmap‘s pixels, and mark the * bitmap as "dead",
meaning it will throw an exception if getPixels() or * setPixels() is called, and will
draw nothing. This operation cannot be * reversed, so it should only be called if you are sure there
are no further uses for the bitmap. This is an advanced call, and normally need * not be called,
since the normal GC process will free up this memory when * there are no more
references to this bitmap. */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(四)释放对象的引用 
描述: 
举两个例子进行说明。 
示例 A : 
假设有如下操作

public class DemoActivity extends Activity {
... ...
private Handler mHandler = ... private Object obj;
public void operation() {
obj = initObj(); ...
mHandler.post(new Runnable() {
public void run() {useObj(obj);}});
}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们有一个成员变量 obj ,在 operation() 中我们希望能够将处理 obj 实例的操作 post 到 
某个线程的 MessageQueue 中。在以上的代码中,即便是 mHandler 所在的线程使用完了 
obj 所引用的对象

,但这个对象仍然不会被垃圾回收掉,因为 DemoActivity.obj 还保有这个对象 的引用。所

以如果在 DemoActivity 中不再使用这个对象了,可以在 [Mark] 的位置释放对象的

用,而代码可以修改为: … …

public void operation() {
obj = initObj(); ...
final Object o = obj;
obj = null;
mHandler.post(new Runnable() {
public void run() {useObj(o);} }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

示例 B: 
假设我们希望在锁屏界面 (LockScreen) 中,监听系统中的电话服务以获取一些信息 ( 如信号强度等 ) ,则可以在 LockScreen 中定义一个 PhoneStateListener 的对象,同时将它注册到 TelephonyManager 服务中。对于 LockScreen 对象,当需要显示锁屏界面的时候就会创 
建一 个 LockScreen 对象,而当锁屏界面消失的时候 LockScreen 对象就会被释放掉。 但是如果在释放 LockScreen 对象的时候忘记取消我们之前注册的 PhoneStateListener 对 象,则会导致 LockScreen 无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会 由于大量的 LockScreen 对象没有办法被回收而引起 OutOfMemory, 使得 system_process 进程 挂掉。总之当一个生命周期较短的对象 A ,被一个生命周期较长的对象 B 保有其引用的情况下,在 A 的生命周期结束时,要在 B 中清除掉对 A 的引用。

(五)其他 
Android 应用程序中最典型的需要注意释放资源的情况是在 Activity 的生命周期中,在onPause() 、 onStop() 、 onDestroy() 方法中需要适当的释放资源的情况。由于此情况很基础, 在此不详细说明,具体可以查看官方文档对 Activity 生命周期的介绍,以明确何时应该释放 哪些资源。

三、不健壮代码的特征及解决办法

1、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域 
后,自动设置为 null,暗示垃圾收集器来收集该对象,防止发生内存泄露。 
对于仍然有指针指向的实例,jvm 就不会回收该资源,因为垃圾回收会将值为 null 的对象 
作为垃圾,提高 GC 回收机制效率;

2、我们的程序里不可避免大量使用字符串处理,避免使用 String,应大量使用

StringBuffer,每一个 String 对象都得独立占用内存一块区域。
String str = "aaa";
String str2 = "bbb";
String str3 = str + str2;//假如执行此次之后 str ,str2 以后再不被调用,那它
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

就会被放在内存中等待 Java 的 gc 去回收,程序内过多的出现这样的情况就会报上面的那 
个错误,建议在使用字符串时能使用 StringBuffer 就不要用 String,这样可以省不少开 
销;

3、尽量少用静态变量,因为静态变量是全局的,GC 不会回收的;

4、避免集中创建对象尤其是大对象,JVM 会突然需要大量内存,这时必然会触发 GC 优化 
系统内存环境;显示的声明数组空间,而且申请数量还极大。 
使用 jspsmartUpload 作文件上传,运行过程中经常出现 java.outofMemoryError 的 
错误,检查之后发现问题:组件里的代码

m_totalBytes = m_request.getContentLength();
m_binArray = new byte[m_totalBytes];//问题原因是 totalBytes 这个变量得
  • 1
  • 2
  • 1
  • 2

到的数极大,导致该数组分配了很多内存空间,而且该数组不能及时释放。

5、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易 
引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理, 
然后解决一块释放一块的策略。

6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用 
hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次 new 
之后又丢弃。

7、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of MemoryError 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

四、内存泄露监测工具 DDMS(Dalvik Debug Monitor Service:Dalvik 虚拟机调试监控服务)

这里我使用 eclipse 的 ADT 插件,并以真机为例,在模拟器中的情况类似。用 Heap 监测应用进程使用内存情况的步骤如下:

  1. 启动 eclipse 后,切换到 DDMS 透视图,并确认 Devices 视图、 Heap 视图都是打开的。
  2. 将手机通过 USB 连接电脑,链接时需要确认手机是处于“ USB 调试”模式,而不是作 
    为“ Mass Storage ”。
  3. 链接成功后,在 DDMS 的 Devices 视图中将会显示手机设备的序列号,以及设备中正 
    在 运行的部分进程信息。
  4. 点击选中想要监测的进程,例讯飞输入法 com.iflytek.inputmethod 进程。
  5. 点击选中 Devices 视图界面中最上方一排图标中的“ Update Heap ”图标。
  6. 点击 Heap 视图中的“ Cause GC ”按钮。
  7. 此时在 Heap 视图中就会看到当前选中的进程的内存使用量的详细情况,如图所示: 

说明: 
a) 点击“ Cause GC ”按钮相当于向虚拟机请求了一次 GC 操作; 
b) 当内存使用信息第一次显示以后,无须再不断的点击“ Cause GC ”, Heap 视图界面会定 时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化。

如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值: Heap 视 图 
中部有一个 Type 叫做 data object ,即数据对象,也就是我们的程序中大量存在的类类型的对象。在 data object 一行中有一列是“ Total Size ”,其值就是当前进程中所有 Java 数据对 象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断: 
a) 不断的操作当前应用,同时注意观察 data object 的 Total Size 值; 
b) 正常情况下 Total Size 值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良 好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象 , 而在虚拟机不断的进行 GC 的过程中,这些对象都被回收了,内存占用量会会落到一个稳定 的水平; 
c) 反之如果代码中存在没有释放对象引用的情况,则 data object 的 Total Size 值在每次GC 后不会有明显的回落,随着操作次数的增多 Total Size 的值会越来越大,直到到达一个上限后导致进程被 kill 掉。

五、内存泄露分析工具 MAT(Memory AnalyzerTool)

MAT 是一个 Eclipse 插件,官方下载地址: http://download.eclipse.org/mat/1.1/update-site/。 

(一)生成 .hprof 文件

  1. 打开 eclipse 并切换到 DDMS 透视图,同时确认 Devices 、 Heap 和 LogCat 视图已经打开。
  2. 将手机设备链接到电脑,并确保使用“ USB 调试”模式链接,而不是“ Mass Storage “模式。
  3. 链接成功后在 Devices 视图中就会看到设备的序列号,和设备中正在运行的部分进程。
  4. 点击 选 中 想 要 分 析 的 应 用 的 进 程 , 在 Devices 视图 上 方 的 一 行 图标 按 钮 中 , 同 时 选中 “ Update Heap ”和“ Dump HPROF file ”两个按钮。此时 DDMS 工具将会自动生成当前选中进程的 .hprof 文件,如图所示: 

(二)使用 MAT 导入 .hprof 文件

在 Eclipse 中点击 Windows->Open Perspective->Other->Memory Analyzer ,或者打 Memory Analyzer Tool 的 RCP 。在 MAT 中点击 File->Open File ,浏览并导入刚刚转换而得到的 .hprof 文件。 

(三)使用 MAT 的视图工具分析内存

  1. 导入 .hprof 文件以后, MAT 会自动解析并生成报告,点击 Dominator Tree ,并按Package 分组。
  2. 选择自己所定义的 Package 类点右键,在弹出菜单中选择 List objects->With incoming references 。这时会列出所有可疑类,右键点击某一项,并选择 Path to GC Roots -> 
    exclude weak/soft references ,会进一步筛选出跟程序相关的所有有内存泄露的类。据此,可以追踪 到代码中的某一个产生泄露的类。 

总之使用 MAT 分析内存查找内存泄漏的根本思路,就是找到哪个类的对象的引用没有 被释放,找到没有被释放的原因,也就可以很容易定位代码中的哪些片段的逻辑有问题了。 

如上图所示,“Problem Suspect”均有可能是代码中存在内存泄露的地方。

时间: 2024-08-24 20:50:08

Android内存泄露调试的相关文章

android 内存泄露调试

一.概述 1 二.Android(Java)中常见的容易引起内存泄漏的不良代码 1 (一) 查询数据库没有关闭游标 2 (二) 构造Adapter时,没有使用缓存的 convertView 3 (三) Bitmap对象不在使用时调用recycle()释放内存 4 (四) 释放对象的引用 4 (五) 其他 5 三.内存监测工具 DDMS --> Heap 5 四.内存分析工具 MAT(Memory Analyzer Tool) 7 (一) 生成.hprof文件 7 (二) 使用MAT导入.hpro

android内存泄露调试,Heap,MAT

三.内存监测工具 DDMS --> Heap 无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方.Android tools中的DDMS就带有一个很不错的内存监测工具Heap(这里我使用eclipse的ADT插件,并以真机为例,在模拟器中的情况类似).用Heap监测应用进程使用内存情况的步骤如下: 1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图.Heap视图都是打开的: 2. 将手机通过USB链接至电

转:Android开发:使用DDMS Heap进行内存泄露调试

无论怎么小心,想完全避免bad code是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方.Android tools中的DDMS就带有一个很不错的内存监测工具Heap,本文讲解了如何使用Heap来进行内存监测. 内存监测工具 DDMS --> Heap 这里我使用eclipse的ADT插件,并以真机为例,在模拟器中的情况类似.用 Heap监测应用进程使用内存情况的步骤如下: 1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图.Heap视图都是

Android内存泄露开篇

先来想这三个问题 内存泄露是怎么回事 内存会泄露的原因 避免内存泄露 1.内存泄露怎么回事 一个程序中,已经不需要使用某个对象,但是因为仍然有引用指向它垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露. Android的一个应用程序的内存泄露对别的应用程序影响不大. 为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进

Android内存泄露总结

Android可能发生内存泄露的地方总结: 1.查询数据库没有关闭游标 2.构建adapter时,没有使用缓存的convertView 3.Bitmap对象不使用的时候调用recycle()方法释放内存 4.释放对象的引用 5.单例模式引用context,如果使用actvitiy-context,会造成内存泄露, 可以使用getApplicationContext()); 或getApplication()代替. 参考文档: A?n?d?r?o?i?d? ?内?存?泄?漏?调?试 http://

记一次内存泄露调试(memory leak)-Driver Monkey

Author:DriverMonkey Mail:[email protected] Phone:13410905075 QQ:196568501 硬件环境:AM335X 软件环境:linux 3.2 现象:1)系统运行一晚上,配置硬件操作失效 2)系统放置在那,没有用户输入会自己死机 调试过程: 第一步:分析硬件配置失效原因,怀疑配置硬件代码有问题 最后发现 代码 调用 system() 函数配置硬件没有调用成功 返回值 为 -1. 第二步: 继续上一步 分析 system() 在什么情况下会

Android内存泄露案例分析

一款优秀的Android应用,不仅要有完善的功能,也要有良好的体验,而性能是影响体验的一个重要因素.内存泄露是Android开发中常见的性能问题.这篇文章,通过我们曾经遇到的一个真实的案例,来讲述一个内存泄露问题,从发现到分析定位,再到最终解决的全过程. 这里把整个过程分为四个阶段: 第一阶段,现场勘查,分析Bug现象,找出有用线索: 第二阶段,初步推断,根据之前的线索,推断可能导致Bug的原因,并且进一步验证推断是否正确: 第三阶段,探究根源,找出导致Bug的真正原因: 第四阶段,解决方案,研

android 内存泄露小计

1   今天在调试android 程序时候,发现即使程序退出了,发现还占用内存大概有15M.用MAT查看,经过多次GC操作,发现依旧是15.直觉告诉我,应该发生内存泄露了.然后利用MAT,查看Memory Leak.结果让我很吃惊,发现是InputMethodManager.这个对象一直引用着Context.也就是Activity,导致它无法释放内存.后来google 一下发现, 以下贴出解决办法,希望给遇到类似情况的人,提供帮助: @Override protected void onDest

Android 内存泄露测试数据处理--procrank,setprop,getprop(转)

1.Android内存测试常用的几个概念. VSS--virtual set size 虚拟耗用内存(包含共享库占用的内存)RSS--Resident set size实际使用的物理内存(包含共享库占用的内存)PSS--Proportional set size 实际使用的物理内存(比例分配共享库占用的内存)USS--Unique Set size:进程独自占用的物理内存(不包含共享库占用的内存)一般来说内存占用大小如下规律:VSS>=RSS>=Pss>=USS 2.Android pr