怎么模拟无限穿越之最牛系统循环添加对象至系统oom

Android处理图片OOM的若干方法小结
众所周知,每个Android应用程序在运行时都有一定的内存限制,限制大小一般为16MB或24MB(视平台而定)。因此在开发应用时需要特别关注自身的内存使用量,而一般最耗内存量的资源,一般是图片、音频文件、视频文件等多媒体资源;由于Android系统对音频、视频等资源做了边解析便播放的处理,使用时并不会把整个文件加载到内存中,一般不会出现内存溢出(以下简称OOM)的错误,因此它们的内存消耗问题暂不在本文的讨论范围。本文重点讨论的是图片的内存消耗问题,如果你要开发的是一款图片浏览器应用,例如像Android系统自带的Gallery那样的应用,这个问题将变得尤为突出;如果你开发的是目前的购物客户端,有时候处理不当也会碰到这种问题。
目前碰到的OOM场景,无外乎以下几种情形,不过无论是哪种情形,解决问题的思路都是一致的。
(1)显示单张图片,图片文件体积达到级别的时候;
(2)在ListView或Gallery等控件中一次性加载大量图片时;
相关知识介绍
1.颜色模型
常见的颜色模型有RGB、YUV、CMYK等,在大多数图像API中采用的都是RGB模型,Android也是如此;另外,在Android中还有包含透明度Alpha的颜色模型,即ARGB。关于颜色模型更加详细的信息暂不在本文的讨论范围之内。
2.计算机中颜色值的数字化编码
在不考虑透明度的情况下,一个像素点的颜色值在计算机中的表示方法有以下3种:
(1)浮点数编码:比如float: (1.0, 0.5, 0.75),每个颜色分量各占1个float字段,其中1.0表示该分量的值为全红或全绿或全蓝;
(2)24位的整数编码:比如24-bit:(255, 128, 196),每个颜色分量各占8位,取值范围0-255,其中255表示该分量的值为全红或全绿或全蓝;
(3)16位的整数编码:比如16-bit:(31, 45, 31),第1和第3个颜色分量各占5位,取值范围0-31,第2个颜色分量占6位,取值范围0-63;
在Java中,float类型的变量占32位,int类型的变量占32位,short和char类型的变量都在16位,因此可以看出,用浮点数表示法编码一个像素的颜色,内存占用量是96位即12字节;而用24位整数表示法编码,只要一个int类型变量,占用4个字节(高8位空着,低24位用于表示颜色);用16位整数表示法编码,只要一个short类型变量,占2个字节;因此可以看出采用整数表示法编码颜色值,可以大大节省内存,当然,颜色质量也会相对低一些。在Android中获取Bitmap的时候一般也采用整型编码。
以上2种整型编码的表示法中,R、G、B各分量的顺序可以是RGB或BGR,Android里采用的是RGB的顺序,本文也都是遵循此顺序来讨论。在24位整型表示法中,由于R、G、B分量各占8位,有时候业内也以RGB888来指代这一信息;类似的,在16位整型表示法中,R、G、B分量分别占5、6、5位,就以RGB565来指代这一信息。
现在再考虑有透明度的颜色编码,其实方式与无透明度的编码方式一样:24位整型编码RGB模型采用int类型变量,其闲置的高8位正好用于放置透明度分量,其中0表示全透明,255表示完全不透明;按照A、R、G、B的顺序,就可以以ARGB8888来概括这一情形;而16位整型编码的RGB模型采用short类型变量,调整各分量所占为数分别至4位,那么正好可以空出4位来编码透明度值;按照A、R、G、B的顺序,就可以以ARGB4444来概括这一情形。回想一下Android的BitmapConfig类中,有ARGB_8888、ARGB_4444、RGB565等常量,现在可以知道它们分别代表了什么含义。同时也可以计算一张图片在内存中可能占用的大小,比如采用ARGB_8888编码载入一张的图片,大概就会占用/.79MB的内存。
3.Bitmap在内存中的存储区域
/2010/08/android-bitmap%E5%86%85%E5%AD%98%E9%99%90%E5%88%B6/ 一文中对Android内存限制问题做了一些探讨,作者认为Bitmap对象通过栈上的引用来指向堆上的Bitmap对象,而Bitmap对象又对应了一个使用了外部存储的native图像,实际上使用的是byte[]来存储的内存空间。但为了确保外部分配内存成功,应该保证当前已分配的内存加上当前需要分配的内存值,大小不能超过当前堆的最大内存值,而且内存管理上将外部内存完全当成了当前堆的一部分。
4.Java对象的引用类型
(1)强引用(StrongReference)如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
(2)软引用(SoftReference)如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
(3)弱引用(WeakReference)弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
(4)虚引用(PhantomReference)&虚引用&顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
解决OOM的常用方案
内存限制是Android对应用的一个系统级限制,作为应用层开发人员,没有办法彻底去消灭这个限制,但是可以通过一些手段去合理使用内存,从而规避这个问题。以下是个人总结的一些常用方法:
(1)缓存图像到内存,采用软引用缓存到内存,而不是在每次使用的时候都从新加载到内存;
(2)调整图像大小,手机屏幕尺寸有限,分配给图像的显示区域本身就更小,有时图像大小可以做适当调整;
(3)采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存;
(4)及时回收图像,如果引用了大量Bitmap对象,而应用又不需要同时显示所有图片,可以将暂时用不到的Bitmap对象及时回收掉;
(5)自定义堆内存分配大小,优化Dalvik虚拟机的堆内存分配;
本文主要将对前面4种方式做演示和分析。
演示试验说明
为了说明出现OOM的场景和解决OOM的方法,本人制作了一个Android应用&&OomDemo来演示,此应用的基本情况说明如下:
(1)该应用展示一个gallery,该gallery只加载图片,gallery的adapter中传入图片的路径而不是图片对象本身,adapter动态加载图片;
(2)演示所用的图片预存储到sdcard的cache目录下,文件名分别为a.jpg,b.jpg&r.jpg,总共18张;
(3)图片为规格的jpg图片,文件大小在423KB-1.48MB范围内;
(4)运行环境:模拟器&&android2.2版本系统&&480*320屏幕尺寸;Moto Defy&&2.3.4版本CM7系统&&854*480屏幕尺寸;
(5)程序基本结构图:
演示结果与说明
首先采用最简单的图片加载方式,不带任何图片缓存、调整大小或者回收,SimpleImageLoader.class便是承担此职责。加载图片部分的代码如下:
public Bitmap loadBitmapImage(String path) {
&&&&&& return BitmapFactory.decodeFile(path);
public Drawable loadDrawableImage(String path) {
&&&&&& return new BitmapDrawable(path);
演示结果:在模拟器上图片只能加载1-3张,之后便会出现OOM错误;在Defy上不会出现错误;原因是两者内存限制不同,Defy上运行的是第三方ROM,内存分配有40MB。另外gallery每次显示一张图片时,都要重新解析获得一张图片,尽管在Defy上还未曾出错,但当图片量加大,GC回收不及时时,还是有可能出现OOM。
为图片加载的添加一个软引用缓存,每次图片从缓存中获取图片对象,若缓存中不存在,才会从Sdcard加载图片,并将该对象加入缓存。同时软引用的对象也有助于GC在内存不足的时候回收它们。ImageLoaderWithCache.class负责这个职责,关键代码如下:
private HashMap&String, SoftReference&Bitmap&& mImageC
&&&&&& @Override
&&&&&& public Bitmap loadBitmapImage(String path) {
&&&&&&&&&&&&& if(mImageCache.containsKey(path)) {
&&&&&&&&&&&&&&&&&&&& SoftReference&Bitmap& softReference = mImageCache.get(path);
&&&&&&&&&&&&&&&&&&&& Bitmap bitmap = softReference.get();
&&&&&&&&&&&&&&&&&&&& if(null != bitmap)
&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&& }
&&&&&&&&&&&&& Bitmap bitmap = BitmapFactory.decodeFile(path);
&&&&&&&&&&&&& mImageCache.put(path, new SoftReference&Bitmap&(bitmap));
&&&&&&&&&&&&&
&&&&&& @Override
&&&&&& public Drawable loadDrawableImage(String path) {
&&&&&&&&&&&&& return new BitmapDrawable(loadBitmapImage(path));
演示结果:在模拟器上,能不无缓存时多加载1-2张图片,但还是会出现OOM;在Defy上不曾出错。由于本次所用的图片都相对比较占内存,在GC还未来得及回收软引用对象时,就又要申请超出剩余量的内存空间,因此仍然没能完全避免OOM。如果换成加载大量的小图片,比如100*100规格的,缓存中软引用的作用可能就发挥出来了。(这一假设可以进一步试验证明一下)
为了进一步避免OOM,除了缓存,还可以对图片进行压缩,进一步节省内存,多数情况下调整图片大小并不会影响应用的表现力。ImageLoaderWithScale.class便是负责这个职责,调整大小的代码如下:
BitmapFactory.Options options = new BitmapFactory.Options();
&&&&&& options.inJustDecodeBounds =
&&&&&& BitmapFactory.decodeFile(path, options);
&&&&&& if (options.mCancel || options.outWidth == -1 || options.outHeight == -1) {
&&&&&&&&&&&&& Log.d(&OomDemo&, &alert!!!& + String.valueOf(options.mCancel) + & & + options.outWidth + options.outHeight);
&&&&&&&&&&&&&
&&&&&& options.inSampleSize = puteSampleSize(options, 600, (int) (1 * 1024 * 1024));
&&&&&& Log.d(&OomDemo&, &inSampleSize: & + options.inSampleSize);
&&&&&& options.inJustDecodeBounds =
&&&&&& options.inDither =
&&&&&& options.inPreferredConfig = Bitmap.Config.ARGB_8888;
&&&&&& Bitmap bitmap = BitmapFactory.decodeFile(path, options);
演示结果:在上述代码中,首先解码图片的边界,在不需要得到Bitmap对象的前提下就能获得图像宽高(宽高值分别被设置到options.outWidth和options.outHeight两个属性中)。computeSampleSize这个方法的参数分别为&解析图片所需的BitmapFactory.Options&、&调整后图片最小的宽或高值&、&调整后图片的内存占用量上限&。结合原始图片的宽高,此方法可以计算得到一个调整比例,再用此比例调整原始图片并加载到内存中,此时图片所消耗的内存不会超出事先指定的大小。在模拟器中,限制图片所占内存大小为1*时,比未压缩过时能加载更多图片,但仍然会出现OOM;若限制图片所占内存大小为0.5*,则能完整的载入所有图片。所以调整图片大小还是能够有效节省内存的。在Defy中不会出错,原因同上。
在有些情况下,严重缩小图片还是会影响应用的显示效果的,所以有必要在尽可能少地缩小图片的前提下展示图片,此时手动去回收图片就变得尤为重要。在类ImageLoaderWithRecyle.class中,便增加了回收图片资源的方法:
&&&&&& public void releaseImage(String path) {
&&&&&&&&&&&&& if(mImageCache.containsKey(path)) {
&&&&&&&&&&&&&&&&&&&& SoftReference&Bitmap& reference = mImageCache.get(path);
&&&&&&&&&&&&&&&&&&&& Bitmap bitmap = reference.get();
&&&&&&&&&&&&&&&&&&&& if(null != bitmap) {
&&&&&&&&&&&&&&&&&&&&&&&&&&& Log.d(&OomDemo&, &recyling & + path);
&&&&&&&&&&&&&&&&&&&&&&&&&&& bitmap.recycle();
&&&&&&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&&&&&&& mImageCache.remove(path);
&&&&&&&&&&&&& }
演示结果:图片压缩限制仍然维持在1*,在adapter中,及时调用releaseImage方法,回收暂时不需要的图片。此时模拟器中也从未出现过OOM,所以总的来讲,综合缓存、调整大小、回收等各种手段,还是能够有效避免OOM的。
本文介绍了软引用缓存、调整大小、回收等手段来避免OOM,总体来说效果还是明显的。但实际应用场景中,图片的应用不想本文所演示的那样简单,有时候图片资源可能来自与网络,这时需要配合异步加载的方式先图片并通过回调的方法来显示;有时候图片资源还需要加边框、加文字等额外修饰,所以在图片加载之后还要另做处理。
另外由于本人能力所限以及时间关系,本文还有诸多不完善之处。比如对Android内存分配的理解不深,没能透彻地解释Bitmap的内存占用情况;通过自定义堆内存分配大小,优化Dalvik的堆内存分配的方法来解决OOM,本文也没有给予演示;再比如在上文的演示试验里,没有把内存占用情况的详细信息用图像形式直观地展示出来;还有演示所用的图片数量过少、规格单一、测试环境偏少,所有没能进行更加严谨科学的对比试验,遗漏了某些意外情况。最后欢迎大家来共同探索、交流并提出建议。
&作者:awp258
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'> Android 系统稳定性 - OOM(二)
Android 系统稳定性 - OOM(二)
javaman163com & &
发布时间: & &
浏览:138 & &
回复:0 & &
悬赏:0.0希赛币
Android 系统稳定性 - OOM(二)
  很久之前写的了,留着有点浪费,共享之。
  编写者:李文栋
2.3 如何分析内存溢出问题
  无论怎么小心,想完全避免 bad code 是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方。
  既然要排查的是内存问题,自然需要与内存相关的工具,DDMS和MAT就是两个非常好的工具。下面详细介绍。
2.3.1 内存监测工具 DDMS --& Heap
  Android tools 中的 DDMS 就带有一个很不错的内存监测工具 Heap(这里我使用 eclipse 的 ADT 插件,并以真机为例,在模拟器中的情况类似)。用 Heap 监测应用进程使用内存情况的步骤如下:
  启动 eclipse 后,切换到 DDMS 透视图,并确认 Devices 视图、Heap 视图都是打开的;
  将手机通过 USB 链接至电脑,链接时需要确认手机是处于“USB 调试”模式,而不是作为“Mass Storage”;
  链接成功后,在 DDMS 的 Devices 视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息;
  点击选中想要监测的进程,比如 system_process 进程;
  点击选中 Devices 视图界面中最上方一排图标中的“Update Heap”图标;
  点击 Heap 视图中的“Cause GC”按钮;
  此时在 Heap 视图中就会看到当前选中的进程的内存使用量的详细情况[如图所示]。
  点击“Cause GC”按钮相当于向虚拟机请求了一次 gc 操作;
  当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap 视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化;
  内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值:Heap 视图中部有一个 Type 叫做 data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在 data object 一行中有一列是“Total Size”,其值就是当前进程中所有 Java 数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断:
  不断的操作当前应用,同时注意观察 data object 的 Total Size 值;
  正常情况下 Total Size 值都会稳定在一个有限的范围内,也就是说由于程序中的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象 ,而在虚拟机不断的进行 GC 的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;
  反之如果代码中存在没有释放对象引用的情况,则 data object 的 Total Size 值在每次 GC后不会有明显的回落,随着操作次数的增多 Total Size 的值会越来越大,直到到达一个上限后导致进程被 kill 掉。
  此处已 system_process 进程为例,在我的测试环境中 system_process 进程所占用的内存的data object 的 Total Size 正常情况下会稳定在 2.2~2.8 之间,而当其值超过 3.55 后进程就会被kill。
  总之,使用 DDMS 的 Heap 视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。
2.3.2 内存分析工具 MAT(Memory Analyzer Tool)
  如果使用 DDMS 确实发现了我们的程序中存在内存泄漏,那又如何定位到具体出现问题的代码片段,最终找到问题所在呢 如果从头到尾的分析代码逻辑,那肯定会把人逼疯,特别是在维护别人写的代码的时候。这里介绍一个极好的内存分析工具 -- Memory Analyzer Tool(MAT)。
  MAT 是一个 Eclipse 插件,同时也有单独的 RCP 客户端。官方下载地址、MAT 介绍和详细的使用教程请参见:/mat,在此不进行说明了。另外在 MAT 安装后的帮助文档里也有完备的使用教程。在此仅举例说明其使用方法。我自己使用的是 MAT 的eclipse 插件,使用插件要比 RCP 稍微方便一些。
  MAT通过解析Hprof文件来分析内存使用情况。HPROF其实是在J2SE5.0中包含的用来分析CPU使用和堆内存占用的日志文件,实质上是虚拟机在某一时刻的内存快照,dalvik中也包含了这样的工具,但是其文件格式和JVM的格式不完全相同,可以用SDK中自带的hprof-conv工具进行转换,例如:
  $./hprof-conv raw.hprof converted.hprof
  可以使用hprof文件配合traceview来分析CPU使用情况(函数调用时间),此处仅仅讨论用它来分析内存使用情况,关于hprof的其他信息可以查看:
  以及Android源码中的/dalvik/docs/heap-profiling.html文件(这个比较重要,建议看看,例如kill -10在Android2.3中已经不支持了)。
  使用 MAT 进行内存分析需要几个步骤,包括:生成.hprof 文件、打开 MAT 并导入hprof文件、使用 MAT 的视图工具分析内存。以下详细介绍。
  1. 生成hprof 文件
  生成hprof 文件的方法有很多,而且 Android 的不同版本中生成hprof 的方式也稍有差别,我使用的版本的是 2.1,各个版本中生成hprof 文件的方法请参考:
  ;a=blob_f=docs/heap-profiling.hb=HEAD。
  (1) 打开 eclipse 并切换到 DDMS 透视图,同时确认 Devices、Heap 和 logcat 视图已经打开了 ;
  (2) 将手机设备链接到电脑,并确保使用“USB 调试”模式链接,而不是“Mass Storage“模式;
  (3) 链接成功后在 Devices 视图中就会看到设备的序列号,和设备中正在运行的部分进程;
  (4) 点击选中想要分析的应用的进程,在 Devices 视图上方的一行图标按钮中,同时选中“Update Heap”和“Dump HPROF file”两个按钮;
  (5) 这是 DDMS 工具将会自动生成当前选中进程的.hprof 文件,并将其进行转换后存放在sdcard 当中,如果你已经安装了 MAT 插件,那么此时 MAT 将会自动被启用,并开始对.hprof文件进行分析;
  注意: (4)步和第(5)步能够正常使用前提是我们需要有 sdcard,并且当前进程有向 sdcard中写入的权限(WRITE_EXTERNAL_STORAGE),否则.hprof 文件不会被生成,在 logcat 中会显示诸如ERROR/dalvikvm(8574): hprof: can't open /sdcard/com.xxx.hprof-hptemp: Permission denied.的信息。
  如果我们没有 sdcard,或者当前进程没有向 sdcard 写入的权限(如 system_process) 那我们可以这样做:
  (6) 在当前程序中,例如 framework 中某些代码中,可以使用 android.os.Debug 中的:
  public static void dumpHprofData(String fileName) throws IOException
  方法,手动的指定.hprof 文件的生成位置。例如:
  xxxButton.setOnClickListener(new View.OnClickListener() {
  public void onClick(View view) {
  android.os.Debug.dumpHprofData("/data/temp/myapp.hprof");
  ... ...
  上述代码意图是希望在 xxxButton 被点击的时候开始抓取内存使用信息,并保存在我们指定的位置:/data/temp/myapp.hprof,这样就没有权限的限制了,而且也无须用 sdcard。但要保证/data/temp 目录是存在的。这个路径可以自己定义,当然也可以写成 sdcard 当中的某个路径。
  如果不确定进程什么时候会OOM,例如我们在跑Monkey的过程中出现了OOM,此时最好的办法就是让程序在出现OOM之后,而没有将OOM的错误信息抛给虚拟机之前就将进程的hprof抓取出来。方法也很简单,只需要在代码中你认为会抛出OutOfMemoryError的地方try...catch,并在catch块中使用android.os.Debug.dumpHprofData(String file)方法就可以请求虚拟机dump出hprof到你指定的文件中。例如我们之前为了排查应用进程主线程中发生的OOM,就在ActivityThread.main()方法中添加了以下代码:
  Looper.loop();
  } catch (OutOfMemoryError e) {
  String file = "path_to_file.hprof"
  ... ...
  android.os.Debug.dumpHprofData(file);
  } catch (IOException e1) {
  e1.printStackTrace();
  在设置hprof的文件路径时,需要考虑权限问题,包括SD卡访问权限、/data分区私有目录访问权限。
  之所以在以上位置添加代码,是因为在应用进程主线程中如果发生异常和错误没有捕获,最终都会从Looper.loop()中抛出来。如果你需要排查在其他线程,或者framework中的OOM问题时,同样可以在适当的位置使用android.os.Debug.dumpHprofData(String file)方法dump hprof文件。
  有了hprof文件,并且用hprof-conv转换格式之后,第二步就可以用MemoryAnalyzerTool(MAT)工具来分析内存使用情况了。
  2. 使用 MAT 导入hprof 文件
  (1) 如果是 eclipse 自动生成的hprof 文件,可以使用 MAT 插件直接打开(可能是比较新的 ADT才支持);
  (2) 如 果 eclipse 自 动 生 成 的 .hprof 文 件 不 能 被 MAT 直 接 打 开 , 或 者 是 使 用android.os.Debug.dumpHprofData()方法手动生成的hprof 文件,则需要将hprof 文件进行转换,转换的方法:
  例如我将hprof 文件拷贝到 PC 上的/ANDROID_SDK/tools 目录下,并输入命令 hprof-conv xxx.hprof yyy.hprof,其中 xxx.hprof 为原始文件,yyy.hprof 为转换过后的文件。转换过后的文件自动放在/ANDROID_SDK/tools 目录下。OK,到此为止,hprof 文件处理完毕,可以用来分析内存泄露情况了。
  (3) 在 Eclipse 中点击 Windows-&Open Perspective-&Other-&Memory Analyzer,或者打 Memory Analyzer Tool 的 RCP。在 MAT 中点击 File-&Open File,浏览并导入刚刚转换而得到的hprof文件。
  3. 使用 MAT 的视图工具分析内存
  导入hprof 文件以后,MAT 会自动解析并生成报告,点击 Dominator Tree,并按 Package分组,选择自己所定义的 Package 类点右键,在弹出菜单中选择 List objects-&With incoming references。这时会列出所有可疑类,右键点击某一项,并选择 Path to GC Roots -& exclude weak/soft references,会进一步筛选出跟程序相关的所有有内存泄露的类。据此,可以追踪到代码中的某一个产生泄露的类。
  MAT 的界面如下图所示。
  了解 MAT 中各个视图的作用很重要,例如 /mat/about/screenshots.php 中介绍的。
  总之使用 MAT 分析内存查找内存泄漏的根本思路,就是找到哪个类的对象的引用没有被释放,找到没有被释放的原因,也就可以很容易定位代码中的哪些片段的逻辑有问题了。下一节将用一个示例来说明MAT详细的使用过程。
2.3.3 MAT使用方法
  1. 构建演示程序
  首先需要构建一个演示程序,并获取hprof文件。程序很简单,按下Button后就循环地new自定义对象SomeObj,并将对象add到ArrayList中,直到抛出OutOfMemoryError,此时会捕获该错误,同时使用android.os.Debug.dumpHprofData方法dump该进程的内存快照到/sdcard/oom.hprof文件中。
  package com.demo.
  import java.io.IOE
  import java.util.ArrayL
  import android.app.A
  import android.os.B
  import android.widget.B
  import android.view.V
  publicclass OOMDemoActivity extends Activity implements View.OnClickListener {
  privatestaticfinal String HPROF_FILE = "/sdcard/oom.hprof";
  private Button mB
  private ArrayList&SomeObj& list = new ArrayList&SomeObj&();
  @Override
  publicvoid onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  mBtn = (Button)findViewById(R.id.btn);
  mBtn.setOnClickListener(this);
  @Override
  publicvoid onClick(View v) {
  while (true) {
  list.add(new SomeObj());
  } catch (OutOfMemoryError e) {
  android.os.Debug.dumpHprofData(HPROF_FILE);
  } catch (IOException e1) {
  e1.printStackTrace();
  private class SomeObj {
  private static final intDATA_SIZE = 1 * 1024 * 1024;
  private byte[]
  SomeObj() {
  data = newbyte[DATA_SIZE];
  因为要写入SDCard,所以要在AndroidManifest.xml中声明WRITE_EXTERNAL_STORAGE的权限。
  注意:演示程序中是使用平台API来获取dump hprof文件的,你也可以使用ADT的DDMS工具来dump。每个hprof都是针对某一个Java进程的,如果你dump的是com.demo.oom进程的hprof,是无法用来分析system_server进程的内存情况的。
  编译并运行程序最终会在SDCard中生成oom.hprof文件,log中会打印相关的日志信息,请留意红色字体:
  I/dalvikvm(1238): hprof: dumping heap strings to "/sdcard/oom.hprof".
  I/dalvikvm(1238): hprof: heap dump completed (21354KB)(虚拟机dump了hprof文件)
  D/dalvikvm(1238): GC_HPROF_DUMP_HEAP freed &1K, 13% free 2K, external 716K/1038K, paused 4034ms
  D/AndroidRuntime(1238): Shutting down VM
  W/dalvikvm(1238): threadid=1: thread exiting with uncaught exception (group=0x)
  E/AndroidRuntime(1238): FATAL EXCEPTION: main
  E/AndroidRuntime(1238): java.lang.OutOfMemoryError(是OOM错误)
  E/AndroidRuntime(1238): at com.demo.oom.OOMDemoActivity$SomeObj.&init&(OOMDemoActivity.java:45)
  E/AndroidRuntime(1238): at com.demo.oom.OOMDemoActivity.onClick(OOMDemoActivity.java:29)
  E/AndroidRuntime(1238): at android.view.View.performClick(View.java:2485)
  E/AndroidRuntime(1238): at android.view.View$PerformClick.run(View.java:9080)
  E/AndroidRuntime(1238): at android.os.Handler.handleCallback(Handler.java:587)
  E/AndroidRuntime(1238): at android.os.Handler.dispatchMessage(Handler.java:92)
  E/AndroidRuntime(1238): at android.os.Looper.loop(Looper.java:123)
  E/AndroidRuntime(1238): at android.app.ActivityThread.main(ActivityThread.java:3683)
  (从方法堆栈可以看到是应用进程的主线程中发生了OOM)
  E/AndroidRuntime(1238): at java.lang.reflect.Method.invokeNative(Native Method)
  E/AndroidRuntime(1238): at java.lang.reflect.Method.invoke(Method.java:507)
  E/AndroidRuntime(1238): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
  E/AndroidRuntime(1238): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
  E/AndroidRuntime(1238): at dalvik.system.NativeStart.main(Native Method)
  W/ActivityManager(61): Force finishing activity com.demo.oom/.OOMDemoActivity
  D/dalvikvm(229): GC_EXPLICIT freed 8K, 55% free K, external 716K/1038K, paused 1381ms
  W/ActivityManager(61): Activity pause timeout for HistoryRecord{ com.demo.oom/.OOMDemoActivity}
  W/ActivityManager(61): Activity destroy timeout for HistoryRecord{ com.demo.oom/.OOMDemoActivity}
  I/Process(1238): Sending signal. PID: 1238 SIG: 9(错误没有捕获被抛给虚拟机,最终被kill掉)
  I/ActivityManager(61): Process com.demo.oom (pid 1238) has died.(应用进程挂掉了)
  获取hprof文件后再用hprof-conv工具转换一下格式:
  D:\work\android\sdk\tools&hprof-conv.exe C:\Users\ray\Desktop\oom.hprof C:\Users
  \ray\Desktop\oom\oom.hprof(将转换后的hprof放到一个单独的目录下,因为分析时会生成很多中间文件)
  2. MAT提供的各种分析工具
  使用MAT导入转换后的hprof文件,导入时会让你选择报告类型,选择“Leak Suspects Report”即可。然后就可以看到如下的初步分析报告:
  MAT在Overview视图中用饼图展示了内存的使用情况,列出了占用内存最大的Java对象com.demo.oom.OOMDemoActivity,我们可以根据这个线索来继续调查,但如果没有这样的提示,也可以根据自己推断来分析。在进一步分析之前,需要先熟悉MAT为我们提供的各种工具。
  (1) Histogram
  列出每个类的实例对象的数量,是第一个非常有用的分析工具。
  可以看到该视图一共有四列,点击列名可以按照不同的列以升序或降序排序。每一列的含义为:
  Class Name:类名
  Objects:每一种类型的对象数量
  Shallow Heap:一个对象本身(不包括该对象引用的其他对象)所占用的内存
  Retained Heap:一个对象本身,以及由该对象引用的其他对象的Shallow Heap的总和。官方文档中解释为:Generally speaking, shallow heap of an object is its size in the heap and retained size of the same object is the amount of heap memory that will be freed when the object is garbage collected.
  默认情况下该视图是按照Class来分类的,也可以点击工具栏中的选择不同的分类类型,这样可以更方便的筛选需要的信息。
  默认情况下该视图只是粗略的计算了每种类型所有对象的Retained Heap,如果要精确计算的话可以点击工具栏中的来选择。
  有时为了分析进程的内存使用情况,会对一个在不同的时间点抓取多个hprof文件来观察,MAT提供了一个非常好的工具来对比这些hprof文件,点击工具栏中的可以选择已经打开的其他hprof文件,选择后MAT将会对当前的hprof和要对比的hprof做一个插值,这样就可以很方便的观察对象的变化了。不过这个工具只有在Histogram视图中才有。
  列表的第一行是一个搜索框,可以输入正则式或者数量来过滤列表的内容。
  (2) Dominator Tree
  列出进程中所有的对象,是第二个非常有用的分析工具。
  和Histogram不同的是左侧列的是对象而不是类(每个对象还有内存地址,例如@0x40516b08),而且还多了Percentage一列。
  右键点击任意一个类型,会弹出一个上下文菜单:
  菜单中有很多其他非常有用的功能,例如:
  List Objects(with outgoing references/with incoming references):列出由该对象引用的其他对象/引用该对象的其他对象;
  Open Source File:打开该对象的源码文件;
  Path To GC Roots:由当前对象到GC Roots引用链
  GC Roots:A garbage collection root is an object that is accessible from outside the heap.也就是指那些不会被垃圾回收的对象。图中标识有黄色圆点的对象就是GC Roots,每个GC Root之后都会有灰黑色的标识表明这个对象之所以是GC Root的原因。使得一个对象成为GC Root的原因一般有以下几个:
  System Class
  Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .
  JNI Local
  Local variable in native code, such as user defined JNI code or JVM internal code.
  JNI Global
  Global variable in native code, such as user defined JNI code or JVM internal code.
  Thread Block
  Object referred to from a currently active thread block.
  Thread
  A started, but not stopped, thread.
  Busy Monitor
  Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.
  Java Local
  Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.
  Native Stack
  In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection.
  Finalizer
  An object which is in a queue awaiting its finalizer to be run.
  Unfinalized
  An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.
  Unreachable
  An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.
  Unknown
  An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.
  在上图的“Path To GC Roots”的菜单中可以选择排除不同的非强引用组合来筛选到GC Roots的引用链,这样就可以知道有哪些GC Roots直接或间接的强引用着当前对象,导致无法释放了。
  (3) Top Consumers
  以class和package分类表示占用内存比较多的对象。
  (4) Leak Suspects
  对内存泄露原因的简单分析,列出了可能的怀疑对象,这些对象可以做为分析的线索。
  (5) OQL
  MAT提供了一种叫做对象查询语言(Object Query Language,OQL)的工具,方便用于按照自己的规则过滤对象数据。例如想查询我的Activity的所有对象:
  SELECT * FROM com.demo.oom.OOMDemoActivity
  或者想查询指定package下的所有对象:
  SELECT * FROM “com.demo.oom.*” (如果使用通配符,需要用引号)
  或者想查询某一个类及其子类的所有对象:
  SELECT * FROM INSTANCEOF android.app.Activity
  还有很多高级的用法请参考帮助文档。
  3. 使用MAT分析OOM原因
  熟悉了以上的各种工具,就可以来分析问题原因了。分析的思路有很多。
  思路一:
  首先我们从MAT的提示中得知com.demo.oom.OOMDemoActivity @ 0x40516b08对象占用了非常多的内存(Shallow Size: 160 B Retained Size: 18 MB),我们可以在DominatorTree视图中查找该对象,或者通过OQL直接查询该类的对象。
  按照Retained Heap降序排列,可以知道OOMDemoActivity对象之所以很大是因为有一个占用内存很大的ArrayList类型的成员变量,而根本原因是这个集合内包含了很多1MB以上的SomeObj对象。此时就可以查看代码中对SomeObj的操作逻辑,查找为什么会有大量SomeObj存在,为什么每个SomeObj都很大。找到问题后想办法解决,例如对SomeObj的存储使用SoftReference,或者减小SomeObj的体积,或者发现是由于SomeObj没有被正确的关闭/释放,或者有其他static的变量引用这SomeObj。
  思路二:
  如果MAT没能给出任何有价值的提示信息,我们可以根据自己的判断来查找可以的对象。因为发生OOM的进程是com.demo.oom,可以使用OQL列出该进程package的所有对象,然后再查找可疑的对象。对应用程序来说,这是非常常用的方法,如下图。
  通过查询发现SomeObj的对象数量特别多,假设正常情况下对象用完后应该立即释放才对,是什么导致这些对象没有被释放呢?通过“Path To GC Roots”的引用链可以知道是OOMDemoActivity中的list引用了SomeObj,所以可以考虑SomeObj是否错误的被添加进了list中,如下图。
  总之,分析的根本目的就是找到那些数量很大或者体积很大的对象,以及他们被什么样的GC Roots引用而没有被释放,然后再通过检查代码逻辑找到问题原因。
本问题标题:
本问题地址:
温馨提示:本问题已经关闭,不能解答。
暂无合适的专家
&&&&&&&&&&&&&&&
希赛网 版权所有 & &&

我要回帖

更多关于 无限之未来系统 的文章

 

随机推荐