【Java精品源码栏目提醒】:网学会员鉴于大家对Java精品源码十分关注,论文会员在此为大家搜集整理了“【精品】Android开发内存泄漏及检查工具使用培训资料 - 其它资料”一文,供大家参考学习
Android 开发内存泄漏及检查工具使用培 训资料 目录1 内存泄露 .............................................................................................................................................. 3 1.1 内存泄露的概念 ..................................................................................................................... 3 1.2 开发人员注意事项 ................................................................................................................. 4 1.3 Android(
java)中常见的引起内存泄露的代码示例 ........................................................ 4 1.3.1 查询数据库没有关闭游标 .......................................................................................... 6 1.3.2 构造 Adapter 时,没有使用缓存的 convertView ................................................... 6 1.3.3 Bitmap 对象不在使用时调用 recycle释放内存 .................................................... 7 1.3.4 释放对象的引用.......................................................................................................... 8 1.3.5 其他 .............................................................................................................................. 92 内存泄露的分析工具 ......................................................................................................................... 9 2.1 内存监测工具 DDMS --gt Heap ............................................................................................ 9 2.2 内存分析工具 MAT Memory Analyzer Tool ................................................................... 10 2.2.1 生成.hprof 文件 ......................................................................................................... 10 2.2.2 使用 MAT 导入.hprof 文件 ...................................................................................... 11 2.2.3 使用 MAT 的视图工具分析内存 ............................................................................ 12 21 内存泄露 Android 应用程序开发以
Java 语言为主,而
Java 编程中一个非常重要但却经常被忽视的问题就是内存使用的问题。
Java 的垃圾回收机制Garbage Collection 以下简称 GC使得很多开发者并不关心内存使用的生命周期,只顾着申请内存,却不手动释放废弃的内存,而造成内存泄露,引起很多问题,甚至程序崩溃。
Android 的虚拟机 Dalvik VM 和
java 虚拟机JVM 没有什么太大的区别,只是在字节码上稍做优化,所以 Android 应用开发中同样会出现内存泄露的问题。
而且由于 Android 智能平台主要用于嵌入式产品开发,可用的内存资源更加稀少,所以对于我们 Android 应用开发人员来说,就更该了解 Android 程序的内存管理机制,避免内存泄露的发生。
1.1 内存泄露的概念 在计算机科学中,内存泄漏memory leak指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。
内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏与许多其他问题有着相似的症状,并且通常情况下只能由那些可以获得程序源代码的程序员才可以分析出来。
然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,严格意义上来说这是不准确的。
一般我们常说的内存泄漏是指堆内存的泄漏。
堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显式释放的内存。
应用程序一般使用 malloc,calloc,realloc,new 等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用 free 或 delete 释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
这里我们只简单的理解,在
java 程序中,如果已经不再使用一个对象,但是仍然有引用指向它,GC 就无法收回它,当然该对象占用的内存就无法再被使用,这就造成内存泄露。
可能一个实例对象的内存泄露很小,并不会引起很大的问题。
但是如果程序反复做此操作或者长期运行,造成内存不断泄露,终究会使程序无内存可用,只好被系统 kill 掉。
在以下情况,内存泄漏导致较严重的后果: 程序运行后置之不理,并且随着时间的流失消耗越来越多的内存(比如服务器上的后台任务,尤其是嵌入式系统中的后台任务,这些任务可能被运行后很多年内都置之不理); 新的内存被频繁地分配,比如当显示电脑游戏或动画视频画面时; 程序能够请求未被释放的内存(比如共享内存),甚至是在程序终止的时候; 泄漏在操作系统内部发生; 泄漏在系统关键驱动中发生; 内存非常有限,比如在嵌入式系统或便携设备中; 当运行于一个终止时内存并不自动释放的操作系统(比如 AmigaOS)之上,而且一旦丢失只能通过重启来恢复。
31.2 开发人员注意事项 对于开发者,对待内存泄露应该以防为主,以治为辅,因为一旦造成内存泄露,追查原因并不容易,虽然有工具可以利用,但是还是会耗费不必要的时间和精力来分析内存使用报告和反复搜查代码。
为了开发高性能和高质量的软件,防止出现豆腐渣工程的出现,我们要知道什么时候用 gc 什么时候用 recycle 以及到底用不用 finalization,因为
Java 对内存的分配只需要 new 开发者不需要显示的释放内存,但是这样造成的内存泄露问题的几率反而更高。
我们还需要: 1.了解
Java 的四种引用方式,比如强引用,软引用,弱引用以及虚引用。
一些复杂些的程序在长期运行很可能出现类似 OutOfMemoryError 的异常。
2.并不要过多的指望 gc,不用的对象可以显示的设置为空,比如 objnull,这里 提示大家,
java 的 gc 使用的是一个有向图,判断一个对象是否有效看的是其他的对象能到达这个对象的顶点,有向图的相对于链表、二叉树来说开销是可想而知。
3.Android 为每个程序分配的对内存可以通过 Runtime 类的 totalMemory freeM emory两 个 方 法 获 取 VM 的 一 些 内 存 信 息 , 对 于 系 统 heap 内 存 获 取 , 可 以 通 过 Dalvik.VMRuntime 类的 getMinimumHeapSize方法获取最小可用堆内存,同时显示释放软引用可以调用该类的 gcSoftReferences方法,获取更多的运行内存。
4.对于多线程的处理,如果并发的线程很多,同时有频繁的创建和释放,可以通过concurrent 类的线程池解决线程创建的效率瓶颈。
5.不要在循环中创建过多的本地变量。
Java 中的引用简介:在
Java 中内存管理,引用分为四大类,强引用 HardReference、弱引用 WeakReference、软引用 SoftReference 和虚引用 PhantomReference。
它们的区别也很明显,HardReference 对象 是 即 使 虚 拟 机 内 存 吃 紧 抛 出 OOM 也 不 会 导 致 这 一 引 用 的 对 象 被 回 收 , 而WeakReference 等更适合于一些数量不多,但体积稍微庞大的对象,在这四个引用中,它是最容易被垃圾回收的,而我们对于显示类似 Android Market 中每个应用的 AppIcon 时可以考虑使用 SoftReference 来解决内存不至于快速回收,同时当内存短缺面临 JavaVM 崩溃抛出 OOM 前时,软引用将会强制回收内存,最后的虚引用一般没有实际意义,仅仅观察 GC的活动状态,对于测试比较实用同时必须和 ReferenceQueue 一起使用。
对于一组数据,我们可以通过 HashMap 的方式来添加一组 SoftReference 对象来临时保留一些数据,同时对于需要反复通过网络获取的不经常改变的内容,可以通过本地的文件系统或数据库来存储缓存。
1.3 Android(
java)中常见的引起内存泄露的代码示例 Android 主要应用在嵌入式设备当中,而嵌入式设备由于一些众所周知的条件限制,通常都不会有很高的配置,特别是内存是比较有限的。
如果我们编写的代码当中有太多的对内存使用不当的地方,难免会使得我们的设备运行缓慢,甚至是死机。
为了能够使得Android 应用程序安全且快速的运行,Android 的每个应用程序都会使用一个专有的 Dalvik虚拟机实例来运行,它是由 Zygote 服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进程中运行的。
一方面,如果程序在运行过程中出现了内存泄漏的问题,仅仅会使得自己的进程被 kill 掉,而不会影响其他进程(如果是 system_process 等系统进程出问 4题的话,则会引起系统重启)。
另一方面 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 0setprop ro.VISIBLE_APP_ADJ 1setprop ro.SECONDARY_SERVER_ADJ 2setprop ro.BACKUP_APP_ADJ 2setprop ro.HOME_APP_ADJ 4setprop ro.HIDDEN_APP_MIN_ADJ 7setprop ro.CONTENT_PROVIDER_ADJ 14setprop ro.EMPTY_APP_ADJ 15 Define the memory thresholds at which the above process classes will be killed. These numbers are in pages 4k.setprop ro.FOREGROUND_APP_MEM 1536setprop ro.VISIBLE_APP_MEM 2048setprop ro.SECONDARY_SERVER_MEM 4096setprop ro.BACKUP_APP_MEM 4096setprop ro.HOME_APP_MEM 4096setprop ro.HIDDEN_APP_MEM 5120setprop ro.CONTENT_PROVIDER_MEM 5632setprop 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 01271415write /proc/sys/vm/overcommit_memory 1write /proc/sys/vm/min_free_order_shift 4write /sys/module/lowmemorykiller/parameters/minfree12056326144 Set init its forked childrens oom_adj.write /proc/1/oom_adj -16 正因为我们的应用程序能够使用的内存有限,所以在编写代码的时候需要特别注意内存使用问题。
如下是一些常见的内存使用不当的情况。
1.3.1 查询数据库没有关闭游标描述: 程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor 后没有关闭的情况。
如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的 5情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
示例代码:Cursor cursor getContentResolver.queryuri ...if cursor.moveToNext ... ...修正示例代码:Cursor cursor nulltry cursor getContentResolver.queryuri ...if cursor null ampamp cursor.moveToNext ... ... finally if cursor null try cursor.close catch Exception e //ignore this1.3.2 构造 Adapter 时,没有使用缓存的 convertView描述: 以构造ListView 的BaseAdapter 为例,在BaseAdapter 中提高了方法:public View getViewint position View convertView ViewGroup parent来向ListView提供每一个item 所需要的view 对象。
初始时ListView 会从BaseAdapter 中根据当前的屏幕布局实例化一定数量的view 对象,同时ListView 会将这些view 对象缓存起来。
当向上滚动ListView 时,原先位于最上面的list item 的view 对象会被回收,然后被用来构造新出现的最下面的list item。
这个构造过程就是由getView方法完成的,getView的第二个形参View convertView 就是被缓存起来的list item 的view 对象初始化时缓存中没有view对象则convertView 是null。
由此可以看出,如果我们不去使用convertView,而是每次都在getView中重新实例化一个View 对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。
ListView回收listitem 的view 对象的过程可以查看:android.widget.AbsListView.
java --gt void addScrapViewView scrap 方法。
示例代码:public View getViewint position View convertView ViewGroup parent View view new Xxx...... ...return view 6修正示例代码:public View getViewint position View convertView ViewGroup parent View view nullif convertView null view convertViewpopulateview getItemposition... else view new Xxx......return view1.3.3 Bitmap 对象不在使用时调用 recycle释放内存描述: 有时我们会手工的操作Bitmap 对象,如果一个Bitmap 对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。
可以看一下代码中的注释:/ Free up the memory associated with this bitmaps pixels and mark the bitmap as quotdeadquot 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.3.4 释放对象的引用描述:这种情况描述起来比较麻烦,举两个例子进行说明。
示例A:假设有如下操作public class DemoActivity extends Activity ... ...private Handler mHandler ...private Object objpublic void operation obj initObj... 7MarkmHandler.postnew Runnable public void run useObjobj我们有一个成员变量obj,在operation中我们希望能够将处理obj 实例的操作post 到某个线程的MessageQueue 中。
在以上的代码中,即便是mHandler 所在的线程使用完了obj所引用的对象,但这个对象仍然不会被垃圾回收掉,因为DemoActivity.obj 还保有这个对象的引用。
所以如果在DemoActivity 中不再使用这个对象了,可以在Mark的位置释放对象的引用,而代码可以修改为:... ...public void operation obj initObj...final Object o objobj nullmHandler.postnew Runnable public void run useObjo... ...示例B: 假设我们希望在锁屏界面LockScreen中,监听系统中的电话服务以获取一些信息如信号强度等,则可以在LockScreen 中定义一个PhoneStateListener 的对象,同时将它注册到TelephonyManager 服务中。
对于LockScreen 对象,当需要显示锁屏界面的时候就会创建一个LockScreen 对象,而当锁屏界面消失的时候LockScreen 对象就会被释放掉。
但是如果在释放LockScreen 对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen 无法被垃圾回收。
如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen 对象没有办法被回收而引起OutOfMemory使得system_process进程挂掉。
总之当一个生命周期较短的对象A,被一个生命周期较长的对象B 保有其引用的情况下,在A 的生命周期结束时,要在B 中清除掉对A 的引用。
1.3.5 其他Android 应用程序中最典型的需要注意释放资源的情况是在 Activity 的生命周期中,在onPause、onStop、onDestroy方法中需要适当的释放资源的情况。
由于此情况很基础,在此不详细说明,具体可以查看官方文档对 Activity 生命周期的介绍,以明确何时应该释放哪些资源。
82 内存泄露的分析工具2.1 内存监测工具 DDMS --gt Heap 无论怎么小心,想完全避免bad code也很困难,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方。
Android tools中的DDMS就带有一个很不错的内存监测工具Heap这里我使用eclipse 的ADT 插件,并以真机为例,在模拟器中的情况类似。
用Heap监测应用进程使用内存情况的步骤如下:1. 启动eclipse后,切换到DDMS 视图,并确认Devices 视图、Heap 视图都是打开的;2.将设备通过USB 链接至电脑,链接时需要确认手机是处于“USB 调试”模式,而不是作为“Mass Storage”;3. 链接成功后,在DDMS 的Devices 视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息;4. 点击选中想要监测的进程,比如system_process 进程;5. 点击选中Devices 视图界面中最上方一排图标中的“Update Heap”图标;6. 点击Heap 视图中的“Cause GC”按钮;7. 此时在Heap 视图中就会看到当前选中的进程的内存使用量的详细情况〔如图所示〕。
说明: a 点击“Cause GC”按钮相当于向虚拟机请求了一次gc 操作; b 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap 视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化; c 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。
如何才能知道我们的程序是否有内存泄漏的可能性呢。
这里需要注意一个值:Heap 视图中部有一个Type 叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。
点击“Data Object”这一行,会产生Allocation count per size的统计图。
在data object一行中有一列是“Total Size”,其值就是当前进程中所有
Java 数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。
可以这样判断: a 不断的操作当前应用,同时注意观察data object 的Total Size 值; b 正常情况下Total Size 值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC 的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平; c 反之如果代码中存在没有释放对象引用的情况,则data object 的Total Size 值在每次GC后不会有明显的回落,随着操作次数的增多Total Size 的值会越来越大,直到到达一个上限后导致进程被kill 掉。
d 此处已system_process 进程为例,在我的测试环境中system_process 进程所占用的内存的data object 的Total Size 正常情况下会稳定在 2.22.8 之间,而当其值超过3.55 后进程就会被kill。
总之,使用DDMS 的Heap 视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。
92.2 内存分析工具 MAT Memory Analyzer Tool 如果使用DDMS 确实发现了我们的程序中存在内存泄漏,那又如何定位到具体出现问题的代码片段,最终找到问题所在呢?如果从头到尾的分析代码逻辑,那肯定会把人逼疯,特别是在维护别人写的代码的时候。
这里介绍一个极好的内存分析工具-- Memory AnalyzerToolMAT。
MAT 是一个Eclipse 插件,同时也有单独的RCP 客户端。
官方下载地址、MAT 介绍和详细的使用教程请参见:www.eclipse.org/mat,在此不进行说明了。
另外在MAT 安装后的帮助文档里也有完备的使用教程。
在此仅举例说明其使用方法。
我自己使用的是MAT 的eclipse 插件,使用插件要比RCP 稍微方便一些。
插件安装地址:http://download.eclipse.org/mat/1.1/update-site,请在eclipse-gtHelp-gtInstall new software中安装 使用MAT 进行内存分析需要几个步骤,包括:生成.hprof 文件、打开MAT 并导入.hprof文件、使用MAT 的视图工具分析内存。
以下详细介绍。
2.2.1 生成.hprof 文件1. 打开eclipse 并切换到DDMS .